diff --git a/scripts/run_benchmarks.sh b/scripts/run_benchmarks.sh new file mode 100755 index 000000000..bf3ad2a7b --- /dev/null +++ b/scripts/run_benchmarks.sh @@ -0,0 +1,365 @@ +#!/bin/bash + +# ============================================================================= +# COMMAND-LINE FLAGS +# +# bash scripts/run_benchmarks.sh [--no-overwrite] [--disable-caching] +# +# --no-overwrite Keep existing result folders: write to _1, _2, +# ... instead of deleting and recreating . +# --disable-caching Run the server with SPYRE_KERNEL_CACHE=0 instead of the +# default 1. Note the folder name does not encode this, so a +# cached and an uncached run of the same config overwrite +# each other unless you also pass --no-overwrite. +# +# ============================================================================= + +echo "Start running experiments" + +# ============================================================================= +# HOW TO USE THIS SCRIPT +# +# param_sets below is the ONLY thing you need to edit. +# +# >>> TO RUN MORE EXPERIMENTS, JUST ADD MORE LINES TO param_sets. <<< +# +# ONE LINE = ONE BENCHMARKING RUN. +# +# For example, to sweep batch size: +# +# param_sets=( +# "bench=aiops ... batch_size=4 ..." +# "bench=aiops ... batch_size=8 ..." +# "bench=aiops ... batch_size=16 ..." +# ) +# +# Each line runs sequentially: start its own vllm server, run the benchmark +# against it, kill the server, move to the next line. +# +# Results go to results//, where the name encodes every +# param below, so different configs never overwrite each other. Re-running the +# same config overwrites it, unless you pass --no-overwrite (then it creates +# ..._1, ..._2, ... alongside). +# +# PARAMS (all are per-experiment; any param documented with a default below may +# be omitted from a param_sets line, and can still be overridden there) +# +# bench Which dataset to run. One of the bench_files keys defined +# further down: cics, db2, ims, aiops, tls, all. +# num_prompts Number of prompts to send from that dataset. +# batch_size Server-side max batched sequences (--max-num-seqs). +# max_context_len Server-side max model length (--max-model-len), in tokens. +# concurrency Client-side max in-flight requests (--max-concurrency). +# Usually set equal to batch_size. +# chunk_size Server-side prefill chunk (--max-num-batched-tokens). +# (optional, default $default_chunk_size) +# prefix_caching 1 = --enable-prefix-caching, 0 = --no-enable-prefix-caching. +# (optional, default $default_prefix_caching) +# shuffle 1 = shuffle the dataset order, 0 = keep dataset order +# (0 passes --disable-shuffle). +# (optional, default $default_shuffle) +# ignore_eos 1 = --ignore-eos, so generation always runs to the full +# output length instead of stopping at EOS. 0 = honor EOS. +# (optional, default $default_ignore_eos) +# custom_output_len Force this many output tokens per request. -1 = use the +# output lengths recorded in the dataset. +# (optional, default $default_custom_output_len) +# tp_size Tensor parallel size (--tensor-parallel-size). +# (optional, default $default_tp_size) +# num_blocks KV cache blocks (--num-gpu-blocks-override). +# (optional, default $default_num_blocks) +# commit e.g. commit=2aaab14 +# Git commit to check out before running this experiment. +# None (or omitted) = stay on the current checkout. +# The working tree must be clean (untracked files are fine). +# +# The single line below is the GOLDEN BENCHMARK -- this is the configuration we +# use to benchmark spyre-inference +# ============================================================================= +param_sets=( + # 10 prompts: useful for populating the cache and doing a test run + "commit=None bench=aiops num_prompts=10 batch_size=4 max_context_len=8192 concurrency=4 tp_size=1 num_blocks=2049" + # GOLDEN BENCHMARK + "commit=None bench=aiops num_prompts=200 batch_size=4 max_context_len=8192 concurrency=4 tp_size=1 num_blocks=2049" +) + +# Parse command line arguments +NO_OVERWRITE=false +KERNEL_CACHE=1 +for arg in "$@"; do + if [ "$arg" == "--no-overwrite" ]; then + NO_OVERWRITE=true + echo "Running with --no-overwrite: will create incremented folders instead of overwriting" + elif [ "$arg" == "--disable-caching" ]; then + KERNEL_CACHE=0 + echo "Running with --disable-caching: SPYRE_KERNEL_CACHE=0" + fi +done + +# try installing libraries for visualization plots +uv pip install pandas matplotlib plotly 2>/dev/null + +# pick up Spyre hardware libs if they've been installed via +# scripts/install-pinned-rpms.sh; needed by both the server and the client +if [ -f ~/spyre-libs/env.sh ]; then + source ~/spyre-libs/env.sh +fi + +export SPYRE_NUM_CPUS=8 +export HF_HOME=/models/huggingface_cache +model=ibm-granite/granite-3.3-8b-instruct + +results_folder=results/ +result_filename=result.json +default_chunk_size=512 +default_prefix_caching=1 +default_shuffle=0 +default_ignore_eos=1 +default_custom_output_len=-1 +default_tp_size=1 +default_num_blocks=2049 +timeout=7200 # wait for up to two hours for the server to get ready + +# remove trailing slashes +results_folder="${results_folder%/}" + +echo -e "\n" +echo "model: $model" +echo "results folder: $results_folder" +echo -e "\n" + +mkdir $results_folder 2>/dev/null + +declare -A bench_files +bench_files["cics"]="/models/online_benchmarking_data_reordered/cics_results_2025.11.03_e2ee1b0_correct_order.jsonl" +bench_files["db2"]="/models/online_benchmarking_data_reordered/db2_results_2025.11.03_e2ee1b0_correct_order.jsonl" +bench_files["ims"]="/models/online_benchmarking_data_reordered/ims_results_2025.11.03_e2ee1b0_correct_order.jsonl" +bench_files["aiops"]="/models/online_benchmarking_data_reordered/aiops_results_2025.11.03_e2ee1b0_correct_order.jsonl" +bench_files["tls"]="/models/online_benchmarking_data_reordered/tls_results_2025.11.03_e2ee1b0_correct_order.jsonl" +bench_files["all"]="/models/online_benchmarking_data_reordered/all_sequences_interleaved.jsonl" + +# Iterate over keys +for param_set in "${param_sets[@]}"; do + + # clear optional params so values don't leak across param sets + unset chunk_size prefix_caching shuffle ignore_eos custom_output_len + unset tp_size num_blocks commit + + # retrieve config params + eval "$param_set" + bench_file=${bench_files[$bench]} + + # fall back to the defaults for any optional param the param_set omitted + for opt in chunk_size prefix_caching shuffle ignore_eos custom_output_len \ + tp_size num_blocks; do + if [ -z "${!opt}" ]; then + eval "$opt=\$default_$opt" + fi + done + + commit_suffix="" + if [ -n "$commit" ] && [ "$commit" != "None" ]; then + # refuse to check out over uncommitted tracked changes; this script + # itself is exempt, since it is the one driving the checkout + self_path=$(git ls-files --full-name "$BASH_SOURCE") + dirty=$(git status --porcelain --untracked-files=no | grep -v " ${self_path}\$") + if [ -n "$dirty" ]; then + echo "Uncommitted changes present, refusing to check out $commit:" + echo "$dirty" + exit 1 + fi + echo "Checking out commit $commit" + # carry this script's own edits across the checkout + self_backup=$(mktemp) + cp "$BASH_SOURCE" "$self_backup" + git checkout -- "$self_path" + if ! git checkout "$commit"; then + cp "$self_backup" "$self_path" + rm -f "$self_backup" + echo "Failed to check out $commit. Skipping this experiment." + continue + fi + cp "$self_backup" "$self_path" + rm -f "$self_backup" + commit_suffix="_$(git rev-parse --short HEAD)" + fi + + experiments_results_base=${results_folder}/${bench}_${num_prompts}_${max_context_len}_bs${batch_size}_conc${concurrency}_chunksize${chunk_size}_pc${prefix_caching}_shuffle${shuffle}_ignoreeos${ignore_eos}_olen${custom_output_len}_tp${tp_size}_nblocks${num_blocks}${commit_suffix} + + # Handle existing directory based on NO_OVERWRITE flag + experiments_results=$experiments_results_base + if [ "$NO_OVERWRITE" = true ]; then + # Check if directory exists and find next available suffix + if [ -d "$experiments_results" ]; then + counter=1 + while [ -d "${experiments_results_base}_${counter}" ]; do + counter=$((counter + 1)) + done + experiments_results="${experiments_results_base}_${counter}" + echo "Directory $experiments_results_base already exists, using $experiments_results instead" + fi + else + # Delete and recreate the folder if it exists + if [ -d "$experiments_results" ]; then + echo "Directory $experiments_results already exists, deleting and recreating" + rm -rf "$experiments_results" + fi + fi + + echo -e "\n" + echo "benchmark: $bench_file" + echo "batch_size: $batch_size" + echo "max context length: $max_context_len" + echo "max active requests: $concurrency" + echo "tensor parallel size: $tp_size" + echo "num gpu blocks override: $num_blocks" + echo "use prefix caching: $prefix_caching" + echo "chunk size: $chunk_size" + echo "shuffle: $shuffle" + echo "results folder: $experiments_results" + echo -e "\n" + + # create result directory + mkdir -p $experiments_results + + if [[ "$prefix_caching" -eq 1 ]]; then + prefix_caching_arg="--enable-prefix-caching" + else + prefix_caching_arg="--no-enable-prefix-caching" + fi + + max_num_batched_tokens_arg="--max-num-batched-tokens $chunk_size" + + # Set shuffle_arg based on shuffle parameter + if [[ "$shuffle" -eq 0 ]]; then + shuffle_arg="--disable-shuffle" + else + shuffle_arg="" + fi + + if [[ "$ignore_eos" -eq 1 ]]; then + ignore_eos_arg="--ignore-eos" + else + ignore_eos_arg="" + fi + + echo -e " + export SPYRE_KERNEL_CACHE=$KERNEL_CACHE + + vllm serve $model + --max-model-len $max_context_len + --max-num-seqs $batch_size + --tensor-parallel-size $tp_size + $max_num_batched_tokens_arg + $prefix_caching_arg > ${experiments_results}/serving_output.txt 2>&1 + " > ${experiments_results}/serving_output.txt + + export SPYRE_KERNEL_CACHE=$KERNEL_CACHE + vllm serve $model \ + --max-model-len $max_context_len \ + --max-num-seqs $batch_size \ + --tensor-parallel-size $tp_size \ + --num-gpu-blocks-override $num_blocks \ + $max_num_batched_tokens_arg \ + $prefix_caching_arg >> ${experiments_results}/serving_output.txt 2>&1 & + + PID=$! # Capture PID + server_start_ts=$(date +%s) + + # Save all bash variables + declare -p > ${experiments_results}/experiment_config.log + + # Loop to check periodically that the server is ready + elapsed=0 + while true; do + echo "Check server ready" + if grep -q "Application startup complete" ${experiments_results}/serving_output.txt; then + + # the polling interval below quantizes this to ~5s + server_ready_sec=$(( $(date +%s) - server_start_ts )) + echo "Server ready in ${server_ready_sec}s" + echo "$server_ready_sec" > ${experiments_results}/server_startup_seconds.txt + + echo "Starting vllm bench serve..." + + echo -e " + vllm bench serve + --backend vllm + --model $model + --save-result + --save-detailed + --result-dir $experiments_results + --result-filename $result_filename + --endpoint /v1/completions + --dataset-name custom + --dataset-path $bench_file + --num-prompts $num_prompts + --skip-chat-template + --metric-percentiles 99,100 + --percentile-metrics ttft,tpot,itl,e2el + --custom-output-len $custom_output_len + $ignore_eos_arg + $shuffle_arg + --max-concurrency $concurrency + " > ${experiments_results}/bench_output.txt + + vllm bench serve \ + --backend vllm \ + --model $model \ + --save-result \ + --save-detailed \ + --result-dir $experiments_results \ + --result-filename $result_filename \ + --endpoint /v1/completions \ + --dataset-name custom \ + --dataset-path $bench_file \ + --num-prompts $num_prompts \ + --skip-chat-template \ + --metric-percentiles 99,100 \ + --percentile-metrics ttft,tpot,itl,e2el \ + --custom-output-len $custom_output_len \ + $ignore_eos_arg \ + $shuffle_arg \ + --max-concurrency $concurrency >> ${experiments_results}/bench_output.txt 2>&1 + + EXIT_STATUS=$? + + if [ "$EXIT_STATUS" -ne 0 ]; then + echo "[$(date)] Exp failed with status $EXIT_STATUS." + fi + + # kill the server + echo "kill the server, PID=${PID}" + sleep 10 + kill $PID + sleep 10 + kill -15 $PID 2>/dev/null + sleep 10 + kill -9 $PID 2>/dev/null + break + + elif grep -qe "ERROR" -qe "vllm: error" ${experiments_results}/serving_output.txt; then + echo "Error detected in server logs. Failing the experiment." + + # We want to wait before killing the server, try ovoiding ghost processes + sleep 10 + kill -15 $(pstree -p $PID | grep -o '([0-9]\+)' | tr -d '()') 2>/dev/null + sleep 10 + kill -9 $(pstree -p $PID | grep -o '([0-9]\+)' | tr -d '()') 2>/dev/null + break + + elif [ "$elapsed" -ge "$timeout" ]; then + echo "Timeout waiting for server ($timeout sec). Failing the experiment." + + # We need to wait before killing the server otherwise my might get ghost processes + sleep 10 + kill -15 $(pstree -p $PID | grep -o '([0-9]\+)' | tr -d '()') 2>/dev/null + sleep 10 + kill -9 $(pstree -p $PID | grep -o '([0-9]\+)' | tr -d '()') 2>/dev/null + sleep 10 + break + fi + sleep 5 # Wait before checking again + elapsed=$((elapsed + 5)) + done +done