#!/bin/bash # 单算子性能测试入口。改配置区或用同名环境变量覆盖: # OP=softmax OP_FILE=softmax SHAPE_FILE=my_shapes.yaml bash run_pytest.sh # 产物落在 runs/_<时间戳>/:run.log、shapes.yaml、autotune_records/、ttgir/ set -euo pipefail # ============================== 配置区 ============================== # 每项均可用同名环境变量覆盖,详见 README「环境变量一览」。 # 被测算子,对应 $FLAGGEMS_DIR/benchmark/test_.py::test_ OP="${OP:-fused_marlin_moe_mxfp4}" OP_FILE="${OP_FILE:-fused_marlin_moe}" # 上游已拆分为两个仓库:主仓 /workspace/dev/FlagGems(包名 flag_gems)与 # /workspace/dev/FlagGems-vllm(包名 flaggems_vllm)。测 vllm 仓库算子时覆盖本变量即可。 FLAGGEMS_DIR="${FLAGGEMS_DIR:-/workspace/dev/FlagGems}" # 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}" # 可选:限制 dtype 集(空格分隔,如 "bfloat16 float16")。空=上游默认 dtype 扫描。 # 算子不支持指定 dtype 时上游会报 "can't be supported by this op"(批量驱动据此降级重试)。 DTYPES="${DTYPES:-}" # 空=record 模式,把本次选中的 config 记入 autotune_records/.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 # 实时输出不缓冲 # DTYPES 非空时逐个转为上游 --dtypes 选项(action=append,每个 dtype 一次) DTYPE_ARGS=() for _dt in $DTYPES; do DTYPE_ARGS+=(--dtypes "$_dt") done # 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" ${DTYPE_ARGS[@]+"${DTYPE_ARGS[@]}"} \ --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"