1e1d612031
For shape-heavy ops the autotune sweep dominates wall time, not the measurement. _parallel_warmup_plugin shards the sweep across N GPUs and hands the merged configs to the normal single-GPU serial measurement, so timing stays comparable. Activated by PARALLEL_WARMUP_GPUS=N (silent no-op when unset, so it can stay in the plugin list); output layout, REPLAY_FROM usage and the latency table are unchanged. Measurement is deliberately not parallelized: N processes saturating one box couple through the power/thermal budget, so per-card latency gets dragged by its neighbours by an amount that does not reproduce. Config keys whose winner differs across shards are counted and reported as a WARNING -- that count is how much to trust the run. Fixes found while auditing: - Shards no longer overwrite CUDA_VISIBLE_DEVICES with a bare shard index, which a caller who had selected idle cards (e.g. 6,7) would see re-interpreted as absolute ids 0,1 -- silently benchmarking on the busy cards they were avoiding. - Interrupting the sweep no longer leaves N subprocesses holding GPUs; children are terminated and reaped before the exception propagates (BaseException, since KeyboardInterrupt is not an Exception). - run_pytest.sh claims its output dir with a bare mkdir and retreats to a -2/-3 suffix on collision. The second-resolution timestamp meant two concurrent runs shared one directory and overwrote each other. - Replay eviction failures now emit a distinct compile_*_no_evict marker. LibTuner.cache is a sqlite-backed ConfigCache with no __delitem__, so the bad config could not be dropped and the retry re-read it, while the log still claimed a clean fallback to live autotune. Also trims comment density in both shell scripts and reworks the README: promotes the parallel and cudagraph sections out from under the A/B flow, groups the env table by purpose, documents that two record-mode runs are not comparable, and marks ab_fold_test.sh as a caliber example whose switch upstream has already removed.
156 lines
5.6 KiB
Bash
156 lines
5.6 KiB
Bash
#!/bin/bash
|
||
# 单算子性能测试入口。改配置区或用同名环境变量覆盖:
|
||
# OP=softmax OP_FILE=softmax SHAPE_FILE=my_shapes.yaml bash run_pytest.sh
|
||
# 产物落在 runs/<op>_<时间戳>/:run.log、shapes.yaml、autotune_records/、ttgir/
|
||
set -euo pipefail
|
||
|
||
# ============================== 配置区 ==============================
|
||
# 每项均可用同名环境变量覆盖,详见 README「环境变量一览」。
|
||
|
||
# 被测算子,对应 $FLAGGEMS_DIR/benchmark/test_<OP_FILE>.py::test_<OP>
|
||
OP="${OP:-fused_marlin_moe_mxfp4}"
|
||
OP_FILE="${OP_FILE:-fused_marlin_moe}"
|
||
|
||
FLAGGEMS_DIR="${FLAGGEMS_DIR:-/workspace/FlagGems-dev}"
|
||
|
||
# shape 来源:非空则用该 yaml,否则用 INLINE_YAML。顶层 key 必须是 op 名。
|
||
SHAPE_FILE="${SHAPE_FILE:-}"
|
||
read -r -d '' INLINE_YAML <<'YAML' || true
|
||
fused_marlin_moe_mxfp4:
|
||
shapes:
|
||
- [1, 256, 4096, 256, 6]
|
||
- [2, 256, 4096, 256, 6]
|
||
- [4, 256, 4096, 256, 6]
|
||
- [8, 256, 4096, 256, 6]
|
||
- [16, 256, 4096, 256, 6]
|
||
- [32, 256, 4096, 256, 6]
|
||
- [64, 256, 4096, 256, 6]
|
||
- [128, 256, 4096, 256, 6]
|
||
- [256, 256, 4096, 256, 6]
|
||
- [512, 256, 4096, 256, 6]
|
||
- [1024, 256, 4096, 256, 6]
|
||
- [2048, 256, 4096, 256, 6]
|
||
- [4096, 256, 4096, 256, 6]
|
||
- [8192, 256, 4096, 256, 6]
|
||
- [16384, 256, 4096, 256, 6]
|
||
- [32768, 256, 4096, 256, 6]
|
||
shape_desc: "num_tokens, num_experts, hidden_size, intermediate_size, topk"
|
||
YAML
|
||
|
||
# 调优空间:0=普通 autotune(默认,快速验证);1=FlagTune 扩展空间(首跑全量搜索、慢)
|
||
USE_FLAGTUNE="${USE_FLAGTUNE:-0}"
|
||
|
||
# 空=record 模式,把本次选中的 config 记入 autotune_records/<op>.json;
|
||
# 指向某次历史 run 目录则 replay 其记录,用于 A/B 两侧锁同一套 config。
|
||
REPLAY_FROM="${REPLAY_FROM:-}"
|
||
|
||
# always/never 强制开关终端颜色,空则按 tty 判断(run.log 始终去色)
|
||
FLAGGEMS_PERF_COLOR="${FLAGGEMS_PERF_COLOR:-}"
|
||
|
||
# 各插件作用见 README「插件说明」;注释掉某行即停用该插件
|
||
PLUGINS=(
|
||
-p _device_guard_plugin
|
||
-p _parallel_warmup_plugin
|
||
-p _seed_plugin
|
||
-p _shape_inject_plugin
|
||
-p _shape_iter_inject_plugin
|
||
-p _bespoke_shape_plugin
|
||
# -p _mm_cluster_fix_plugin
|
||
-p _autotune_record_plugin
|
||
-p _cudagraph_plugin
|
||
-p _pretty_report_plugin
|
||
-p _ir_meta_plugin
|
||
)
|
||
|
||
# ============================== 执行逻辑 ==============================
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
TEST_FILE="$FLAGGEMS_DIR/benchmark/test_${OP_FILE}.py::test_${OP}"
|
||
|
||
# 产物目录,可用 OUT_DIR 指定。时间戳只到秒,并发启动会撞名,故用不带 -p 的
|
||
# mkdir 抢占(已存在即失败),撞上就退让到 -2、-3……
|
||
if [[ -n "${OUT_DIR:-}" ]]; then
|
||
mkdir -p "$OUT_DIR"
|
||
else
|
||
mkdir -p "$SCRIPT_DIR/runs"
|
||
BASE="$SCRIPT_DIR/runs/${OP}_$(date +%Y%m%d-%H%M%S)"
|
||
OUT_DIR="$BASE"
|
||
n=1
|
||
until mkdir "$OUT_DIR" 2>/dev/null; do
|
||
n=$((n + 1))
|
||
OUT_DIR="${BASE}-${n}"
|
||
(( n > 99 )) && { echo "!!! 无法创建产物目录($BASE 及后缀均被占用)" >&2; exit 1; }
|
||
done
|
||
fi
|
||
LOG_FILE="$OUT_DIR/run.log"
|
||
|
||
# tee 管道会让 python 侧检测不到 tty,故在 shell 层判断后经环境变量下传
|
||
if [[ -z "$FLAGGEMS_PERF_COLOR" && -t 1 && -z "${NO_COLOR:-}" ]]; then
|
||
FLAGGEMS_PERF_COLOR=always
|
||
fi
|
||
export FLAGGEMS_PERF_COLOR
|
||
if [[ "$FLAGGEMS_PERF_COLOR" == "always" ]]; then
|
||
C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'
|
||
C_DIM=$'\033[2m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m'
|
||
PYTEST_COLOR=(--color=yes)
|
||
else
|
||
C_RED='' C_GREEN='' C_YELLOW='' C_DIM='' C_BOLD='' C_RESET=''
|
||
PYTEST_COLOR=()
|
||
fi
|
||
|
||
export PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}"
|
||
export FLAGGEMS_PERF_CURRENT_OP="$OP"
|
||
export PYTHONUNBUFFERED=1 # 实时输出不缓冲
|
||
|
||
# record/replay 互斥,各由自己的环境变量激活
|
||
if [[ -n "$REPLAY_FROM" ]]; then
|
||
AUTOTUNE_ENV="FLAGGEMS_PERF_AUTOTUNE_REPLAY_DIR=$REPLAY_FROM/autotune_records"
|
||
[[ -f "$REPLAY_FROM/autotune_records/$OP.json" ]] || \
|
||
echo "${C_YELLOW}>>> warning: $REPLAY_FROM/autotune_records/$OP.json 不存在;replay 将回退为现场 autotune${C_RESET}" >&2
|
||
MODE_DESC="replay($REPLAY_FROM)"
|
||
else
|
||
AUTOTUNE_ENV="FLAGGEMS_PERF_AUTOTUNE_RECORD_DIR=$OUT_DIR/autotune_records"
|
||
mkdir -p "$OUT_DIR/autotune_records"
|
||
MODE_DESC=record
|
||
fi
|
||
|
||
# 未指定 shape 文件时,把 INLINE_YAML 落到临时文件供 pytest 读取
|
||
if [[ -z "$SHAPE_FILE" ]]; then
|
||
SHAPE_FILE="$(mktemp --suffix=.yaml)"
|
||
printf '%s\n' "$INLINE_YAML" > "$SHAPE_FILE"
|
||
trap 'rm -f "$SHAPE_FILE"' EXIT
|
||
fi
|
||
cp -f "$SHAPE_FILE" "$OUT_DIR/shapes.yaml" # 存档本次实际使用的 shape
|
||
|
||
status=0
|
||
{
|
||
echo "${C_BOLD}>>> op=$OP mode=$MODE_DESC USE_FLAGTUNE=$USE_FLAGTUNE${C_RESET}"
|
||
echo "${C_DIM}>>> out=$OUT_DIR${C_RESET}"
|
||
|
||
# 每次用独立的 Triton 缓存目录,跑完即删:保证编译过程可复现,且 ttgir 落盘
|
||
# 只包含本次的变体(_ir_meta_plugin 在 atexit 里按 shape 整理)。
|
||
CACHE_DIR="$OUT_DIR/.triton_cache"
|
||
rm -rf "$CACHE_DIR"; mkdir -p "$CACHE_DIR"
|
||
status=0
|
||
TRITON_CACHE_DIR="$CACHE_DIR" \
|
||
FLAGGEMS_PERF_TTGIR_DUMP_DIR="$OUT_DIR/ttgir" \
|
||
env "$AUTOTUNE_ENV" \
|
||
USE_FLAGTUNE=$USE_FLAGTUNE python -u -m pytest -s "$TEST_FILE" \
|
||
"${PLUGINS[@]}" "${PYTEST_COLOR[@]}" \
|
||
--shape_file "$SHAPE_FILE" \
|
||
--level core --mode kernel || status=$?
|
||
|
||
rm -rf "$CACHE_DIR"
|
||
|
||
if (( status == 0 )); then
|
||
echo "${C_GREEN}>>> done. outputs in $OUT_DIR${C_RESET}"
|
||
else
|
||
echo "${C_RED}>>> FAILED (pytest exit $status). partial outputs in $OUT_DIR${C_RESET}"
|
||
fi
|
||
exit "$status"
|
||
} 2>&1 | tee "$LOG_FILE" || status=$?
|
||
|
||
# run.log 去掉 ANSI 转义以便 grep/diff(终端输出保留颜色;Ctrl-C 时会跳过这步)
|
||
sed -i -E $'s/\x1b\\[[0-9;]*[A-Za-z]//g' "$LOG_FILE"
|
||
exit "$status"
|