diff --git a/benchmarks/llm-d/Dockerfile b/benchmarks/llm-d/Dockerfile index 8494027604..cfc04557c5 100644 --- a/benchmarks/llm-d/Dockerfile +++ b/benchmarks/llm-d/Dockerfile @@ -22,7 +22,7 @@ # --build-arg EPP_FROM_IMAGE="$EPP_FROM_IMAGE" \ # --build-arg ROUTING_SIDECAR_IMAGE="$ROUTING_SIDECAR_IMAGE" \ # --build-arg ENVOY_FROM_IMAGE="$ENVOY_FROM_IMAGE" ... -ARG VLLM_BASE=vllm/vllm-openai:v0.23.0 +ARG VLLM_BASE=vllm/vllm-openai:v0.26.0 ARG EPP_FROM_IMAGE=ghcr.io/llm-d/llm-d-router-endpoint-picker:v0.9.0 ARG ROUTING_SIDECAR_IMAGE=ghcr.io/llm-d/llm-d-router-disagg-sidecar:v0.9.0 ARG ENVOY_FROM_IMAGE=envoyproxy/envoy:distroless-v1.33.2 diff --git a/benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-low-latency.yaml b/benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-low-latency.yaml index f0cd876e4c..e29106062d 100644 --- a/benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-low-latency.yaml +++ b/benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-low-latency.yaml @@ -18,7 +18,7 @@ # decode ep:1/dp-attn:false in the master key is what selects the # pure-TP path in server.sh. Decode knobs are latency-sized: max-num-seqs # 256, max-num-batched-tokens 256, max-cudagraph-capture-size 256, -# FULL_DECODE_ONLY graphs, stream-interval 50, gpu-memory-utilization 0.9. +# FULL_DECODE_ONLY graphs, gpu-memory-utilization 0.9. # 2. PREFILL is IDENTICAL to the mid-curve/high-tpt prefill (copied verbatim, # DEP8 with enable-ep-weight-filter + moe-backend deep_gemm_mega_moe). An # earlier srt-slurm-style low-latency prefill (weight-offload, no filter, @@ -51,7 +51,7 @@ plugins: - type: prefix-cache-scorer - type: queue-scorer - type: active-request-scorer - - type: max-score-picker + - type: weighted-random-picker schedulingProfiles: - name: prefill @@ -63,12 +63,13 @@ schedulingProfiles: weight: 2 - pluginRef: active-request-scorer weight: 2 - - pluginRef: max-score-picker + - pluginRef: weighted-random-picker - name: decode plugins: - pluginRef: decode-filter - pluginRef: active-request-scorer - - pluginRef: max-score-picker + weight: 2 + - pluginRef: weighted-random-picker dataLayer: discovery: @@ -89,7 +90,10 @@ prefill: extra-args: >- --kv-cache-dtype fp8 --enforce-eager - --gpu-memory-utilization 0.9 + --gpu-memory-utilization 0.95 + --max-model-len 9280 + --max-num-seqs 16 + --max-num-batched-tokens 32768 --enable-cumem-allocator --no-enable-prefix-caching --no-async-scheduling @@ -98,6 +102,7 @@ prefill: --moe-backend deep_gemm_mega_moe --enable-ep-weight-filter --no-disable-hybrid-kv-cache-manager + --no-enable-flashinfer-autotune --numa-bind env: NCCL_CUMEM_ENABLE: "1" @@ -123,14 +128,16 @@ prefill: NVSHMEM_REMOTE_TRANSPORT: "none" NVSHMEM_ENABLE_NIC_PE_MAPPING: "1" NVSHMEM_HCA_PE_MAPPING: "mlx5_0:1:1,mlx5_1:1:1,mlx5_3:1:1,mlx5_4:1:1" - VLLM_USE_NCCL_SYMM_MEM: "1" + VLLM_USE_NCCL_SYMM_MEM: "0" VLLM_SPARSE_INDEXER_MAX_LOGITS_MB: "1024" VLLM_MAX_TOKENS_PER_EXPERT_FP4_MOE: "2048" TILELANG_CLEANUP_TEMP_FILES: "1" - TORCH_SYMMMEM: "NVSHMEM" + NVSHMEM_CUMEM_HANDLE_TYPE: "FABRIC" + NVSHMEM_DISABLE_CUDA_VMM: "0" VLLM_SKIP_P2P_CHECK: "1" - VLLM_RANDOMIZE_DP_DUMMY_INPUTS: "0" + VLLM_RANDOMIZE_DP_DUMMY_INPUTS: "1" VLLM_USE_DEEP_GEMM: "1" + VLLM_USE_RUST_FRONTEND: "1" NVIDIA_GDRCOPY: "enabled" VLLM_HTTP_TIMEOUT_KEEP_ALIVE: "120" # Harden distributed coordination against transient gloo/NCCL resets under @@ -156,7 +163,6 @@ decode: --no-enable-flashinfer-autotune --block-size 256 --compilation-config {"cudagraph_mode":"FULL_DECODE_ONLY","mode":0} - --stream-interval 50 --tokenizer-mode deepseek_v4 --no-disable-hybrid-kv-cache-manager --enable-sleep-mode @@ -171,9 +177,13 @@ decode: # rc added so KV falls back to InfiniBand, not TCP (see prefill note). UCX_TLS: "cuda_copy,cuda_ipc,rc,tcp" UCX_CUDA_IPC_ENABLE_MNNVL: "y" - VLLM_USE_NCCL_SYMM_MEM: "1" + VLLM_USE_NCCL_SYMM_MEM: "0" TILELANG_CLEANUP_TEMP_FILES: "1" + NVSHMEM_CUMEM_HANDLE_TYPE: "FABRIC" + NVSHMEM_DISABLE_CUDA_VMM: "0" VLLM_SKIP_P2P_CHECK: "1" + VLLM_RANDOMIZE_DP_DUMMY_INPUTS: "1" + VLLM_USE_RUST_FRONTEND: "1" NVIDIA_GDRCOPY: "enabled" TORCH_DISTRIBUTED_DEFAULT_TIMEOUT: "1800" diff --git a/benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-mid-curve-megamoe.yaml b/benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-mid-curve-megamoe.yaml index 2103ecf5c7..f82a79f28c 100644 --- a/benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-mid-curve-megamoe.yaml +++ b/benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-mid-curve-megamoe.yaml @@ -45,7 +45,7 @@ plugins: - type: prefix-cache-scorer - type: queue-scorer - type: active-request-scorer - - type: max-score-picker + - type: weighted-random-picker schedulingProfiles: - name: prefill @@ -57,12 +57,13 @@ schedulingProfiles: weight: 2 - pluginRef: active-request-scorer weight: 2 - - pluginRef: max-score-picker + - pluginRef: weighted-random-picker - name: decode plugins: - pluginRef: decode-filter - pluginRef: active-request-scorer - - pluginRef: max-score-picker + weight: 2 + - pluginRef: weighted-random-picker dataLayer: discovery: @@ -87,7 +88,10 @@ prefill: extra-args: >- --kv-cache-dtype fp8 --enforce-eager - --gpu-memory-utilization 0.9 + --gpu-memory-utilization 0.95 + --max-model-len 9280 + --max-num-seqs 16 + --max-num-batched-tokens 32768 --enable-cumem-allocator --no-enable-prefix-caching --no-async-scheduling @@ -96,6 +100,7 @@ prefill: --moe-backend deep_gemm_mega_moe --enable-ep-weight-filter --no-disable-hybrid-kv-cache-manager + --no-enable-flashinfer-autotune --numa-bind env: NCCL_CUMEM_ENABLE: "1" @@ -121,14 +126,16 @@ prefill: NVSHMEM_REMOTE_TRANSPORT: "none" NVSHMEM_ENABLE_NIC_PE_MAPPING: "1" NVSHMEM_HCA_PE_MAPPING: "mlx5_0:1:1,mlx5_1:1:1,mlx5_3:1:1,mlx5_4:1:1" - VLLM_USE_NCCL_SYMM_MEM: "1" + VLLM_USE_NCCL_SYMM_MEM: "0" VLLM_SPARSE_INDEXER_MAX_LOGITS_MB: "1024" VLLM_MAX_TOKENS_PER_EXPERT_FP4_MOE: "2048" TILELANG_CLEANUP_TEMP_FILES: "1" - TORCH_SYMMMEM: "NVSHMEM" + NVSHMEM_CUMEM_HANDLE_TYPE: "FABRIC" + NVSHMEM_DISABLE_CUDA_VMM: "0" VLLM_SKIP_P2P_CHECK: "1" - VLLM_RANDOMIZE_DP_DUMMY_INPUTS: "0" + VLLM_RANDOMIZE_DP_DUMMY_INPUTS: "1" VLLM_USE_DEEP_GEMM: "1" + VLLM_USE_RUST_FRONTEND: "1" NVIDIA_GDRCOPY: "enabled" VLLM_HTTP_TIMEOUT_KEEP_ALIVE: "120" # Harden distributed coordination against transient gloo/NCCL connection @@ -146,12 +153,13 @@ decode: --max-num-seqs 512 --max-num-batched-tokens 512 --max-cudagraph-capture-size 512 - --gpu-memory-utilization 0.85 + --gpu-memory-utilization 0.9 + --max-model-len 9280 --enable-cumem-allocator --no-enable-prefix-caching + --no-enable-flashinfer-autotune --block-size 256 --compilation-config {"cudagraph_mode":"FULL_DECODE_ONLY","mode":0} - --stream-interval 50 --tokenizer-mode deepseek_v4 --moe-backend deep_gemm_mega_moe --enable-ep-weight-filter @@ -170,12 +178,14 @@ decode: NVSHMEM_REMOTE_TRANSPORT: "none" NVSHMEM_ENABLE_NIC_PE_MAPPING: "1" NVSHMEM_HCA_PE_MAPPING: "mlx5_0:1:1,mlx5_1:1:1,mlx5_3:1:1,mlx5_4:1:1" - VLLM_USE_NCCL_SYMM_MEM: "1" + VLLM_USE_NCCL_SYMM_MEM: "0" TILELANG_CLEANUP_TEMP_FILES: "1" - TORCH_SYMMMEM: "NVSHMEM" + NVSHMEM_CUMEM_HANDLE_TYPE: "FABRIC" + NVSHMEM_DISABLE_CUDA_VMM: "0" VLLM_SKIP_P2P_CHECK: "1" - VLLM_RANDOMIZE_DP_DUMMY_INPUTS: "0" + VLLM_RANDOMIZE_DP_DUMMY_INPUTS: "1" VLLM_USE_DEEP_GEMM: "1" + VLLM_USE_RUST_FRONTEND: "1" NVIDIA_GDRCOPY: "enabled" # See prefill note: harden the decode's cross-node DP8/EP8 coordination # against transient gloo resets under the conc4096 warmup burst. diff --git a/configs/nvidia-master.yaml b/configs/nvidia-master.yaml index 73a83024c9..7853626445 100644 --- a/configs/nvidia-master.yaml +++ b/configs/nvidia-master.yaml @@ -5107,7 +5107,7 @@ dsv4-fp4-gb200-dynamo-vllm: # TODO: change image to official llmd image. # Build source: benchmarks/llm-d/Dockerfile. dsv4-fp4-gb200-llmd-vllm: - image: ghcr.io/ezrasilvera/llm-d-nokube-vllm:vllm0.23 + image: quay.io/rh-ee-imarkov/llm-d-nokube-vllm:vllm0.26@sha256:a9095d4c835935c4070be2040de0a5ef3b44098f603092ab66d743b0e731b7b4 model: deepseek-ai/DeepSeek-V4-Pro model-prefix: dsv4 runner: gb200 @@ -5143,28 +5143,6 @@ dsv4-fp4-gb200-llmd-vllm: - "DECODE_NODES=2" - "GPUS_PER_NODE=4" - # Low-middle: 1 prefill DEP8 + 4 decode TP8. - - spec-decoding: "none" - conc-list: [256, 512] - prefill: - num-worker: 1 - tp: 1 - ep: 8 - dp-attn: true - additional-settings: - - "PREFILL_NODES=2" - - "GPUS_PER_NODE=4" - - "CONFIG_FILE=dsv4-fp4-gb200-low-latency.yaml" - decode: - num-worker: 4 - tp: 8 - ep: 1 - dp-attn: false - additional-settings: - - "DECODE_NODES=8" - - "DECODE_WORKERS=4" - - "GPUS_PER_NODE=4" - # Mid curve: 1 prefill DEP8 + 1 decode DEP8. - spec-decoding: "none" conc-list: [256, 512, 1024] diff --git a/perf-changelog.yaml b/perf-changelog.yaml index 4dd819f46d..abae8b7b42 100644 --- a/perf-changelog.yaml +++ b/perf-changelog.yaml @@ -5460,3 +5460,13 @@ - "Use vllm/vllm-openai:nightly-387189c42997b27e2c04b5d97ef8190ffa2bf909 with prefix caching enabled, default KV-cache dtype, FlashInfer TRT-LLM attention with FP8 indexer KV, and the EAGLE3 drafter on FLASH_ATTN." pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2328 + +- config-keys: + - dsv4-fp4-gb200-llmd-vllm + description: + - "Decode: gpu-memory-utilization 0.85->0.9, add max-model-len 9280, disable NCCL symmetric memory, add no-enable-flashinfer-autotune, drop stream-interval (unsupported by rust frontend)" + - "Prefill: gpu-memory-utilization 0.9->0.95, add max-model-len 9280, add max-num-seqs 16 + max-num-batched-tokens 32768, enable rust frontend, enable randomize-dp-dummy-inputs" + - "EPP: switch from max-score-picker to weighted-random-picker for better load distribution under high concurrency" + - "Low-latency recipe: same prefill optimizations, decode NCCL symmetric memory disabled + rust frontend enabled, drop stream-interval" + - "Sweep: drop low-middle point (c256/512, 1P×4D) from llmd-vllm key; low-latency recipe runs at c1 only" + pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2498 diff --git a/runners/launch_gb200-nv.sh b/runners/launch_gb200-nv.sh index 672ccd0414..a54f568d32 100755 --- a/runners/launch_gb200-nv.sh +++ b/runners/launch_gb200-nv.sh @@ -16,6 +16,89 @@ SQUASH_DIR="/mnt/lustre01/users-public/sa-shared" POWER_SRT_SLURM_URL="https://github.com/edwingao28/srt-slurm.git" POWER_SRT_SLURM_PIN="6fc1bed01a0b82dae0088a105c03ce0cfb353443" +# Enroot 3.x does not parse Docker's tag@digest syntax. For digest-pinned +# images, use its explicit registry syntax and pass the digest as the +# manifest reference so the import remains immutable. +enroot_uri_for_image() { + local image="$1" + local image_without_digest="$image" + local digest="" + local first_component registry repository repository_dir repository_name + + if [[ "$image" == *@sha256:* ]]; then + image_without_digest="${image%@*}" + digest="${image##*@}" + fi + + first_component="${image_without_digest%%/*}" + if [[ "$image_without_digest" == */* && ( "$first_component" == *.* || "$first_component" == *:* || "$first_component" == "localhost" ) ]]; then + registry="$first_component" + repository="${image_without_digest#*/}" + else + registry="registry-1.docker.io" + repository="$image_without_digest" + fi + + if [[ -z "$digest" ]]; then + if [[ "$registry" == "registry-1.docker.io" ]]; then + printf 'docker://%s\n' "$image" + else + printf 'docker://%s#%s\n' "$registry" "$repository" + fi + return + fi + + repository_dir="${repository%/*}" + repository_name="${repository##*/}" + repository_name="${repository_name%%:*}" + if [[ "$repository" == */* ]]; then + repository="${repository_dir}/${repository_name}" + else + repository="$repository_name" + fi + if [[ "$registry" == "registry-1.docker.io" && "$repository" != */* ]]; then + repository="library/$repository" + fi + + printf 'docker://%s#%s:%s\n' "$registry" "$repository" "$digest" +} + +# Concurrent matrix jobs import to the same shared-FS squash path. +# Serialize imports and atomically replace invalid images so readers never +# observe a partially written squash file. +import_squash() { + local squash="$1" image="$2" + local lock="${squash}.lock" + local tmp="${squash}.tmp.$$" + local enroot_uri + enroot_uri=$(enroot_uri_for_image "$image") || exit 1 + ( + exec 9>"$lock" + flock -w 1800 9 || { echo "Failed to acquire lock for $squash" >&2; exit 1; } + if unsquashfs -l "$squash" > /dev/null 2>&1; then + echo "Squash file already exists and is valid, skipping import: $squash" + else + local enroot_runtime + enroot_runtime=$(mktemp -d "${TMPDIR:-/tmp}/enroot-import.XXXXXX") || exit 1 + trap 'rm -rf -- "$enroot_runtime"' EXIT + export ENROOT_RUNTIME_PATH="$enroot_runtime" + + rm -f "$squash" "$squash".tmp.* + if ! enroot import -o "$tmp" "$enroot_uri"; then + rm -f "$tmp" + echo "Error: enroot import failed for $enroot_uri" >&2 + exit 1 + fi + if ! unsquashfs -l "$tmp" > /dev/null 2>&1; then + rm -f "$tmp" + echo "Error: enroot import produced an invalid squash file: $tmp" >&2 + exit 1 + fi + mv -f "$tmp" "$squash" || exit 1 + fi + ) || exit 1 +} + if [[ "$FRAMEWORK" == "llmd-vllm" ]]; then if [[ "$MODEL_PREFIX" == "dsv4" && "$PRECISION" == "fp4" ]]; then export MODEL_PATH="/mnt/numa1/models/DeepSeek-V4-Pro" @@ -26,31 +109,7 @@ if [[ "$FRAMEWORK" == "llmd-vllm" ]]; then fi SQUASH_FILE="${SQUASH_DIR}/$(echo "$IMAGE" | sed 's/[\/:@#]/_/g').sqsh" - - # Enroot uses '#' between the registry and repository, e.g. docker://ghcr.io#org/image:tag. - case "$IMAGE" in - */*) - _registry="${IMAGE%%/*}" - _rest="${IMAGE#*/}" - if [[ "$_registry" == *.* || "$_registry" == *:* ]]; then - ENROOT_URL="docker://${_registry}#${_rest}" - else - ENROOT_URL="docker://${IMAGE}" # bare hub repo - fi - ;; - *) ENROOT_URL="docker://${IMAGE}" ;; - esac - echo "ENROOT_URL=$ENROOT_URL" - - if [[ ! -s "$SQUASH_FILE" ]]; then - echo "enroot import -> $SQUASH_FILE" - enroot import -o "$SQUASH_FILE" "$ENROOT_URL" || { - echo "Error: enroot import failed for $ENROOT_URL" >&2 - exit 1 - } - else - echo "Reusing existing squash: $SQUASH_FILE" - fi + import_squash "$SQUASH_FILE" "$IMAGE" export LLMD_CONTAINER_ENGINE=pyxis export LLMD_SQUASH_FILE="$SQUASH_FILE" @@ -207,84 +266,6 @@ uses_watchtower_shared_fs() { SQUASH_FILE="${SQUASH_DIR}/$(echo "$IMAGE" | sed 's/[\/:@#]/_/g').sqsh" NGINX_SQUASH_FILE="${SQUASH_DIR}/$(echo "$NGINX_IMAGE" | sed 's/[\/:@#]/_/g').sqsh" -# Enroot 3.x does not parse Docker's tag@digest syntax. For digest-pinned -# images, use its explicit registry syntax and pass the digest as the -# manifest reference so the import remains immutable. -enroot_uri_for_image() { - local image="$1" - local image_without_digest="$image" - local digest="" - local first_component registry repository repository_dir repository_name - - if [[ "$image" == *@sha256:* ]]; then - image_without_digest="${image%@*}" - digest="${image##*@}" - fi - - first_component="${image_without_digest%%/*}" - if [[ "$image_without_digest" == */* && ( "$first_component" == *.* || "$first_component" == *:* || "$first_component" == "localhost" ) ]]; then - registry="$first_component" - repository="${image_without_digest#*/}" - else - registry="registry-1.docker.io" - repository="$image_without_digest" - fi - - if [[ -z "$digest" ]]; then - if [[ "$registry" == "registry-1.docker.io" ]]; then - printf 'docker://%s\n' "$image" - else - printf 'docker://%s#%s\n' "$registry" "$repository" - fi - return - fi - - repository_dir="${repository%/*}" - repository_name="${repository##*/}" - repository_name="${repository_name%%:*}" - if [[ "$repository" == */* ]]; then - repository="${repository_dir}/${repository_name}" - else - repository="$repository_name" - fi - if [[ "$registry" == "registry-1.docker.io" && "$repository" != */* ]]; then - repository="library/$repository" - fi - - printf 'docker://%s#%s:%s\n' "$registry" "$repository" "$digest" -} - -# Concurrent matrix jobs import to the same shared-FS squash path. -# Serialize imports and atomically replace invalid images so readers never -# observe a partially written squash file. -import_squash() { - local squash="$1" image="$2" - local lock="${squash}.lock" - local tmp="${squash}.tmp.$$" - local enroot_uri - enroot_uri=$(enroot_uri_for_image "$image") || exit 1 - ( - exec 9>"$lock" - flock -w 1800 9 || { echo "Failed to acquire lock for $squash" >&2; exit 1; } - if unsquashfs -l "$squash" > /dev/null 2>&1; then - echo "Squash file already exists and is valid, skipping import: $squash" - else - rm -f "$squash" "$squash".tmp.* - if ! enroot import -o "$tmp" "$enroot_uri"; then - rm -f "$tmp" - echo "Error: enroot import failed for $enroot_uri" >&2 - exit 1 - fi - if ! unsquashfs -l "$tmp" > /dev/null 2>&1; then - rm -f "$tmp" - echo "Error: enroot import produced an invalid squash file: $tmp" >&2 - exit 1 - fi - mv -f "$tmp" "$squash" || exit 1 - fi - ) || exit 1 -} - import_squash "$SQUASH_FILE" "$IMAGE" import_squash "$NGINX_SQUASH_FILE" "$NGINX_IMAGE"