multimodal_disagg.sh 7.01 KB
Newer Older
1
#!/bin/bash
2
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
# SPDX-License-Identifier: Apache-2.0
4
#
5
6
# Multimodal E/P/D: separate encoder, prefill, and decode workers.
# Default: 3 GPUs (one per worker). Use --single-gpu to co-locate all on GPU 0.
7

8
9
10
11
set -e
trap 'echo Cleaning up...; kill 0' EXIT

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
12
13
source "$SCRIPT_DIR/../../../common/gpu_utils.sh"   # build_gpu_mem_args
source "$SCRIPT_DIR/../../../common/launch_utils.sh" # print_launch_banner, wait_any_exit
14
15
16
17
18
19

# Default values
MODEL_NAME="Qwen/Qwen2.5-VL-7B-Instruct"
CHAT_TEMPLATE="qwen2-vl"
PROVIDED_CHAT_TEMPLATE=""

20
21
22
23
24
25
# --single-gpu: Packs all workers (encode, prefill, decode) onto a single GPU.
# This is intended for functional testing with small models (e.g. 2B) where CI
# only has 1 GPU available. It uses lower mem-fraction-static values to share the GPU
# and enables memory-saving options.
SINGLE_GPU=false

26
27
28
29
30
31
32
# Parse command line arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --model)
            MODEL_NAME=$2
            shift 2
            ;;
33
34
35
36
        --served-model-name)
            SERVED_MODEL_NAME=$2
            shift 2
            ;;
37
38
39
40
        --chat-template)
            PROVIDED_CHAT_TEMPLATE=$2
            shift 2
            ;;
41
42
43
44
        --single-gpu)
            SINGLE_GPU=true
            shift
            ;;
45
46
47
48
        -h|--help)
            echo "Usage: $0 [OPTIONS]"
            echo "Options:"
            echo "  --model <model_name> Specify the model to use (default: $MODEL_NAME)"
49
            echo "  --served-model-name <served_model_name> Specify the served model name to use (default: empty)"
50
            echo "  --chat-template <template> Specify the SGLang chat template to use (default: $CHAT_TEMPLATE)"
51
            echo "  --single-gpu         Pack all workers on 1 GPU (for small models, e.g. 2B)"
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
            echo "  -h, --help           Show this help message"
            exit 0
            ;;
        *)
            echo "Unknown option: $1"
            echo "Use --help for usage information"
            exit 1
            ;;
    esac
done

# Set CHAT_TEMPLATE if provided
if [[ -n "$PROVIDED_CHAT_TEMPLATE" ]]; then
    CHAT_TEMPLATE="$PROVIDED_CHAT_TEMPLATE"
fi

68
69
70
71
72
# Prepare served-model-name argument if provided
SERVED_MODEL_ARG=""
if [[ -n "$SERVED_MODEL_NAME" ]]; then
    SERVED_MODEL_ARG="--served-model-name $SERVED_MODEL_NAME"
fi
73

74
75
76
77
78
79
80
81
82
83
84
# GPU assignments (override via environment variables)
if [[ "$SINGLE_GPU" == "true" ]]; then
    DYN_ENCODE_WORKER_GPU=${DYN_ENCODE_WORKER_GPU:-0}
    DYN_PREFILL_WORKER_GPU=${DYN_PREFILL_WORKER_GPU:-0}
    DYN_DECODE_WORKER_GPU=${DYN_DECODE_WORKER_GPU:-0}
else
    DYN_ENCODE_WORKER_GPU=${DYN_ENCODE_WORKER_GPU:-0}
    DYN_PREFILL_WORKER_GPU=${DYN_PREFILL_WORKER_GPU:-1}
    DYN_DECODE_WORKER_GPU=${DYN_DECODE_WORKER_GPU:-2}
fi

85
86
87
88
89
90
91
92
93
94
95
96
# Per-worker CUDA_VISIBLE_DEVICES pinning. In single-gpu mode, inherit from parent
# (the parallel test runner sets CUDA_VISIBLE_DEVICES); overriding would defeat GPU assignment.
if [[ "$SINGLE_GPU" == "true" ]]; then
    _ENCODE_CUDA_PIN=""
    _PREFILL_CUDA_PIN=""
    _DECODE_CUDA_PIN=""
else
    _ENCODE_CUDA_PIN="CUDA_VISIBLE_DEVICES=$DYN_ENCODE_WORKER_GPU"
    _PREFILL_CUDA_PIN="CUDA_VISIBLE_DEVICES=$DYN_PREFILL_WORKER_GPU"
    _DECODE_CUDA_PIN="CUDA_VISIBLE_DEVICES=$DYN_DECODE_WORKER_GPU"
fi

97
98
99
100
101
# GPU memory fractions for workers (used with --mem-fraction-static)
DYN_ENCODE_GPU_MEM=${DYN_ENCODE_GPU_MEM:-0.9}
DYN_PREFILL_GPU_MEM=${DYN_PREFILL_GPU_MEM:-0.9}
DYN_DECODE_GPU_MEM=${DYN_DECODE_GPU_MEM:-0.9}

102
GPU_MEM_ARGS=$(build_gpu_mem_args sglang --workers-per-gpu 3)
103

104
105
106
107
108
109
110
111
112
ENCODE_EXTRA_ARGS=""
PREFILL_EXTRA_ARGS=""
DECODE_EXTRA_ARGS=""

if [[ "$SINGLE_GPU" == "true" ]]; then
    # 3 workers share one GPU. --max-total-tokens caps the KV cache to a small
    # functional-test size so the last worker can initialize without OOM.
    # --context-length keeps the per-request token pool allocation small.
    ENCODE_EXTRA_ARGS=""
113
114
    PREFILL_EXTRA_ARGS="--mem-fraction-static ${DYN_PREFILL_GPU_MEM} --delete-ckpt-after-loading --max-running-requests 2 --context-length 2048 --max-total-tokens 1024 $GPU_MEM_ARGS"
    DECODE_EXTRA_ARGS="--mem-fraction-static ${DYN_DECODE_GPU_MEM} --delete-ckpt-after-loading --max-running-requests 2 --context-length 2048 --max-total-tokens 1024 $GPU_MEM_ARGS"
115
116
117
118
119
120
fi

# Prevent port collisions: the test framework exports DYN_SYSTEM_PORT which all
# child processes would inherit. Unset it so only workers that need it set their own.
unset DYN_SYSTEM_PORT

121
122
DISAGG_BOOTSTRAP_PORT="${DYN_DISAGG_BOOTSTRAP_PORT:-12345}"

123
HTTP_PORT="${DYN_HTTP_PORT:-8000}"
124
print_launch_banner --multimodal "Launching Disaggregated Multimodal E/P/D" "$MODEL_NAME" "$HTTP_PORT"
125

126
# run ingress
127
128
# dynamo.frontend accepts either --http-port flag or DYN_HTTP_PORT env var (defaults to 8000)
python3 -m dynamo.frontend &
129

130
# run SGLang multimodal encode worker (frontend-facing: encodes images, routes to worker)
131
132
echo "Starting encode worker on GPU $DYN_ENCODE_WORKER_GPU (GPU mem: $DYN_ENCODE_GPU_MEM)..."
DYN_SYSTEM_PORT=${DYN_SYSTEM_PORT1:-8081} \
133
env ${_ENCODE_CUDA_PIN:+"$_ENCODE_CUDA_PIN"} python3 -m dynamo.sglang --multimodal-encode-worker --model-path "$MODEL_NAME" $SERVED_MODEL_ARG --chat-template "$CHAT_TEMPLATE" --skip-tokenizer-init $ENCODE_EXTRA_ARGS &
134
135
136
137
138
139
140

if [[ "$SINGLE_GPU" == "true" ]]; then
    # Wait for encode worker to initialize before starting prefill worker.
    # This prevents workers from competing for GPU memory simultaneously, which can cause OOM.
    echo "Waiting for encode worker to initialize..."
    sleep 5
fi
141
142

# run SGLang multimodal prefill worker
143
144
# TODO: Remove disable-radix-cache once the issue is fixed.
# See https://github.com/sgl-project/sglang/pull/11203.
145
146
echo "Starting prefill worker on GPU $DYN_PREFILL_WORKER_GPU (GPU mem: $DYN_PREFILL_GPU_MEM)..."
DYN_SYSTEM_PORT=${DYN_SYSTEM_PORT2:-8082} \
147
env ${_PREFILL_CUDA_PIN:+"$_PREFILL_CUDA_PIN"} python3 -m dynamo.sglang \
148
149
  --multimodal-worker \
  --model-path "$MODEL_NAME" \
150
  $SERVED_MODEL_ARG \
151
152
153
154
155
  --page-size 16 \
  --tp 1 \
  --trust-remote-code \
  --skip-tokenizer-init \
  --disaggregation-mode prefill \
156
  --disaggregation-bootstrap-port "$DISAGG_BOOTSTRAP_PORT" \
157
  --host 0.0.0.0 \
158
  --disable-radix-cache \
159
160
161
162
163
164
165
166
  --disaggregation-transfer-backend nixl \
  $PREFILL_EXTRA_ARGS &

if [[ "$SINGLE_GPU" == "true" ]]; then
    # Wait for prefill worker to initialize before starting decode worker.
    echo "Waiting for prefill worker to initialize..."
    sleep 5
fi
167
168

# run SGLang multimodal decode worker
169
echo "Starting decode worker on GPU $DYN_DECODE_WORKER_GPU (GPU mem: $DYN_DECODE_GPU_MEM)..."
170
env ${_DECODE_CUDA_PIN:+"$_DECODE_CUDA_PIN"} python3 -m dynamo.sglang \
171
172
  --multimodal-worker \
  --model-path "$MODEL_NAME" \
173
  $SERVED_MODEL_ARG \
174
175
176
177
178
  --page-size 16 \
  --tp 1 \
  --trust-remote-code \
  --skip-tokenizer-init \
  --disaggregation-mode decode \
179
  --disaggregation-bootstrap-port "$DISAGG_BOOTSTRAP_PORT" \
180
  --host 0.0.0.0 \
181
182
  --disaggregation-transfer-backend nixl \
  $DECODE_EXTRA_ARGS &
183

184
185
# Exit on first worker failure; kill 0 in the EXIT trap tears down the rest
wait_any_exit