bench: let ab_rlc_test.sh decide which operators are worth six rounds

zl_bench already had the screening mechanics — ab_rlc_test.sh runs exactly one
config-pinned pair per operator (before records, after replays it) against the
vendor-prefixed RLC switch. I had started a second script for the same job
instead of extending this one; that is removed.

What the existing script could not tell you is whether the pair is worth
following with six rounds, which costs 13-44 minutes per operator against
about 0.8 for the pair. It now writes a verdict per operator to
<root>/screen.tsv, and three of the four are not "the switch does nothing":

  IR_CHANGED  the two sides' ttgir differ         -> measure it
  NO_IR_DIFF  byte-identical, configs pinned      -> skip it
  NO_KERNEL   rows but no .ttgir                  -> never reached Triton here
  NO_ROWS     no measurements                     -> usually the dtype

The criterion is the ttgir fingerprint, not the convert_layout count: equal
counts do not mean equal generated code, and what the A/B measures is whether
the generated code changed. Counts are still recorded — they say what kind of
change it was. The fingerprint comes from _ir_fingerprint.sh, shared with
ab_repeat.sh's round-1 check so the two cannot drift apart.

OPS entries take an optional per-operator dtype and default to the operator's
own set. Forcing one dtype across a list is how fft got written off as
unrunnable on MUSA: it only accepts complex64.

Output moves under runs/ab_rlc_<ts>/<op>_{before,after} with OUT_DIR set
explicitly, rather than recovering the directory with `ls -dt runs/<op>_*`.
That lookup is correct while one process runs operators in series, which is
the only way it has been used; it stops being correct the moment several cards
share runs/.
This commit is contained in:
Aharrypotter committed 2026-09-10 16:54:26 +08:00
1 parent 2fbfe1dd16
commit 4d69320139
3 files changed
+91 -130

No files matched your search

+10 -9
View File
@@ -140,37 +140,38 @@ ttgir 关键设计:
## 两步:先筛选,再实测 ## 两步:先筛选,再实测
跑一个算子的六轮 A/B 要 13~44 分钟(S5000 实测:`rand` 15 shape 13.0 分钟, 跑一个算子的六轮 A/B 要 13~44 分钟(S5000 实测:`rand` 15 shape 13.0 分钟,
`sort` 25 shape 43.4 分钟)。筛一个算子只要约 0.8 分钟(21 个算子一遍 15.0 分钟)。 `sort` 25 shape 43.4 分钟)。用 `ab_rlc_test.sh` 筛一对只要约 0.8 分钟。
**差 20~50 倍**,所以先筛后测: **差 20~50 倍**,所以先筛后测:
```bash ```bash
# 第一步:筛掉不值得排队的算子 # 第一步:一对定去留(A 侧 record,B 侧 REPLAY_FROM 它,config 锁死)
SWITCH=FLAGTREE_MUSA_RLC_ENHANCE ZL_BACKEND=musa FLAGGEMS_DIR=/path/to/FlagGems OPS="col2im:col2im topk:topk perf_fft:fft" bash screen.sh OPS="col2im:col2im topk:topk perf_fft:fft" VENDOR=MUSA bash ab_rlc_test.sh
# 第二步:只把 IR_CHANGED 的送进来 # 第二步:只把 IR_CHANGED 的送进来
SWITCH=FLAGTREE_MUSA_RLC_ENHANCE ROUNDS=6 OPS="topk:topk" bash ab_repeat.sh SWITCH=FLAGTREE_MUSA_RLC_ENHANCE ROUNDS=6 OPS="topk:topk" bash ab_repeat.sh
``` ```
`screen.sh` 对每个算子给四种判定之一: `ab_rlc_test.sh` 除了原有的 speedup 区间,还给每个算子写一行判定到
`<root>/screen.tsv`:
| 判定 | 含义 | 该怎么办 | | 判定 | 含义 | 该怎么办 |
|---|---|---| |---|---|---|
| `IR_CHANGED` | 两侧 ttgir 不同 | 排六轮 A/B | | `IR_CHANGED` | 两侧 ttgir 不同 | 排六轮 A/B |
| `NO_IR_DIFF` | 逐字节相同(config 已锁死) | 跳过。跑六轮只会测到噪声,并报成一个"无差异"结论 | | `NO_IR_DIFF` | 逐字节相同(config 已锁死) | 跳过。跑六轮只会测到噪声,并报成一个"无差异"结论 |
| `NO_KERNEL` | 跑出了行,但没编出 `.ttgir` | 该算子在本后端没走 Triton,编译器开关无从谈起。**不是"开关无作用"** | | `NO_KERNEL` | 跑出了行,但没编出 `.ttgir` | 该算子在本后端没走 Triton,编译器开关无从谈起。**不是"开关无作用"** |
| `NO_ROWS` | 没有测量行 | 先修可运行性:多半是 dtype 不被该算子接受,或该算子在本后端拿不到 config | | `NO_ROWS` | 没有测量行 | 先修可运行性:多半是 dtype 不被该算子接受,或它在本后端拿不到 config |
三件容易做错的: 两件容易做错的:
- **筛选也是 A/B,口径一样严。** A 侧 record、B 侧 `REPLAY_FROM` 它。两侧各自
record 会各自 sweep 出不同 config,IR 自然不同,于是每个算子都判成
`IR_CHANGED`——筛了等于没筛。
- **判据是 ttgir 指纹,不是 `convert_layout` 计数。** cvt 数相同不等于生成代码 - **判据是 ttgir 指纹,不是 `convert_layout` 计数。** cvt 数相同不等于生成代码
相同,而要测的是"生成代码变了没有"。cvt 数照样记,它解释的是变化的性质。 相同,而要测的是"生成代码变了没有"。cvt 数照样记,它解释的是变化的性质。
- **不要全局写死一个 `DTYPES`。** `OPS` 元素支持 `OP:OP_FILE:DTYPES`,留空表示用 - **不要全局写死一个 `DTYPES`。** `OPS` 元素支持 `OP:OP_FILE:DTYPES`,留空表示用
该算子自己的全套。`fft` 只吃 complex64,被一刀切的 `DTYPES=float16` 判死过一次 该算子自己的全套。`fft` 只吃 complex64,被一刀切的 `DTYPES=float16` 判死过一次
——那是 harness 的问题,被记成了算子的问题。 ——那是 harness 的问题,被记成了算子的问题。
同一份 `_ir_fingerprint.sh` 也被 `ab_repeat.sh` 用于第 1 轮后的对比轴有效性检查,
两处判据不会漂开。
## A/B 对比测试标准流程 ## A/B 对比测试标准流程
```bash ```bash
+81 -18
View File
@@ -10,15 +10,34 @@
# #
# 跑完用 batch_summary.py --ab <before> <after> 得到该算子的 speedup 区间。 # 跑完用 batch_summary.py --ab <before> <after> 得到该算子的 speedup 区间。
# #
# 这一轮同时充当 ab_repeat.sh 的筛选前置:一对的代价约 0.8 分钟,六轮 A/B 是
# 13~44 分钟(S5000 实测),差 20~50 倍。所以先用它过一遍,只把 IR_CHANGED 的
# 算子送进 ab_repeat.sh。判定写在 <root>/screen.tsv,四种之一:
#
# IR_CHANGED 两侧 ttgir 不同 -> 排六轮
# NO_IR_DIFF 逐字节相同(config 已锁死)-> 跳过,跑六轮只会测到噪声
# NO_KERNEL 跑出行但没编出 .ttgir -> 该算子在本后端没走 Triton,
# 编译器开关无从谈起。不是"开关无作用"
# NO_ROWS 没有测量行 -> 先修可运行性,多半是 dtype
#
# 判据是 ttgir 指纹而不是 convert_layout 计数:cvt 数相同不等于生成代码相同。
# cvt 数照样记,它解释的是变化的性质。
#
# 用法: # 用法:
# OPS="softmax gelu_backward" bash ab_rlc_test.sh # OPS="softmax gelu_backward" bash ab_rlc_test.sh
# OPS="sort:perf_sort topk" bash ab_rlc_test.sh # 冒号分隔 OP:OP_FILE # OPS="sort:perf_sort topk" bash ab_rlc_test.sh # OP:OP_FILE
# OPS="perf_fft:fft:complex64 topk:topk" bash ab_rlc_test.sh # 再加 DTYPES
# #
# 注意:RLC 开关名带厂商前缀,用 VENDOR 指定(默认 MUSA)。套用前先确认开关有效: # DTYPES 留空表示用该算子自己的全套。**不要全局写死一个 dtype**:fft 只吃
# grep 该开关名于 FlagTree 源码应有命中,否则两侧跑的是同一份代码。 # complex64,被一刀切的 DTYPES=float16 判死过一次——那是 harness 的问题,
# 却被记成了算子的问题。
#
# 注意:RLC 开关名带厂商前缀,用 VENDOR 指定(默认 MUSA)。开关名拼错时两侧跑
# 同一份代码,此时判定会是 NO_IR_DIFF——先查拼写,再下"开关无作用"的结论。
set -uo pipefail set -uo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "./_ir_fingerprint.sh"
VENDOR="${VENDOR:-MUSA}" VENDOR="${VENDOR:-MUSA}"
ENHANCE_VAR="FLAGTREE_${VENDOR}_RLC_ENHANCE" ENHANCE_VAR="FLAGTREE_${VENDOR}_RLC_ENHANCE"
@@ -30,34 +49,77 @@ if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
export FLAGGEMS_PERF_COLOR=always export FLAGGEMS_PERF_COLOR=always
fi fi
LOG="runs/ab_rlc_$(date +%Y%m%d-%H%M%S).log" # 显式 OUT_DIR,不再用 `ls -dt runs/<op>_*` 猜"最新那个就是我刚跑的"。串行下
mkdir -p runs # 那个假设成立,多卡并行共用 runs/ 就不成立了。
ROOT="runs/ab_rlc_$(date +%Y%m%d-%H%M%S)"
mkdir -p "$ROOT"
LOG="$ROOT/summary.log"
TSV="$ROOT/screen.tsv"
printf 'op op_file dtypes rows_before rows_after kernels_before kernels_after cvt_before cvt_after verdict
' > "$TSV"
count_cvt() { find "$1/ttgir" -name '*.ttgir' -type f -exec cat {} + 2>/dev/null | grep -c convert_layout || true; }
count_kernels() { find "$1/ttgir" -name '*.ttgir' -type f 2>/dev/null | wc -l; }
count_rows() { grep -c '^SUCCESS' "$1/run.log" 2>/dev/null || true; }
{ {
echo ">>> vendor=$VENDOR switch=$ENHANCE_VAR ops=$OPS" echo ">>> vendor=$VENDOR switch=$ENHANCE_VAR ops=$OPS"
for entry in $OPS; do for entry in $OPS; do
op="${entry%%:*}" op="${entry%%:*}"; rest="${entry#*:}"
op_file="${entry#*:}" if [[ "$rest" == "$entry" ]]; then op_file="$op"; dtypes=""
[[ "$op_file" == "$entry" ]] && op_file="$op" else
op_file="${rest%%:*}"; d="${rest#*:}"
[[ "$d" == "$rest" ]] && dtypes="" || dtypes="$d"
fi
BEFORE="$ROOT/${op}_before"; AFTER="$ROOT/${op}_after"
echo "########## $op | $ENHANCE_VAR=0 before (record) ##########" echo "########## $op | $ENHANCE_VAR=0 before (record) ##########"
if ! OP="$op" OP_FILE="$op_file" env "$ENHANCE_VAR=0" bash run_pytest.sh; then OP="$op" OP_FILE="$op_file" OUT_DIR="$BEFORE" DTYPES="$dtypes" \
echo "!!! FAILED before: $op" env "$ENHANCE_VAR=0" bash run_pytest.sh
continue rc_before=$?
fi
BEFORE=$(ls -dt "runs/${op}_"* | head -1)
echo "########## $op | $ENHANCE_VAR=1 after (replay $BEFORE) ##########" echo "########## $op | $ENHANCE_VAR=1 after (replay $BEFORE) ##########"
if ! OP="$op" OP_FILE="$op_file" REPLAY_FROM="$BEFORE" env "$ENHANCE_VAR=1" bash run_pytest.sh; then OP="$op" OP_FILE="$op_file" OUT_DIR="$AFTER" DTYPES="$dtypes" \
echo "!!! FAILED after: $op" REPLAY_FROM="$BEFORE" env "$ENHANCE_VAR=1" bash run_pytest.sh
rc_after=$?
rb=$(count_rows "$BEFORE"); ra=$(count_rows "$AFTER")
kb=$(count_kernels "$BEFORE"); ka=$(count_kernels "$AFTER")
cb=$(count_cvt "$BEFORE"); ca=$(count_cvt "$AFTER")
fb=$(ir_fingerprint "$BEFORE") || fb=""
fa=$(ir_fingerprint "$AFTER") || fa=""
if (( rc_before != 0 || rc_after != 0 )) || [[ "${rb:-0}" == 0 || "${ra:-0}" == 0 ]]; then
verdict=NO_ROWS
note="跑不出测量行(rc=$rc_before/$rc_after)。看 run.log——多半是 dtype 不被该算子接受,或它在本后端拿不到 config"
elif [[ -z "$fb" || -z "$fa" ]]; then
verdict=NO_KERNEL
note="没编出 .ttgir:该算子在本后端没走 Triton,编译器开关对它无从谈起。这不是'开关无作用'"
elif [[ "$fb" == "$fa" ]]; then
verdict=NO_IR_DIFF
note="两侧 ttgir 逐字节相同(config 已锁死):开关没改变生成代码。先查开关名拼写,再下结论"
else
verdict=IR_CHANGED
note="生成代码有变(cvt ${cb}→${ca},指纹 ${fb:0:8}/${fa:0:8}):值得排六轮 A/B"
fi
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$op" "$op_file" "${dtypes:-<all>}" "${rb:-0}" "${ra:-0}" \
"$kb" "$ka" "${cb:-0}" "${ca:-0}" "$verdict" >> "$TSV"
echo ">>> $op: $verdict — $note"
if [[ "$verdict" == NO_ROWS ]]; then
echo "!!! $op 没有可对比的测量,不出 speedup 区间"
continue continue
fi fi
AFTER=$(ls -dt "runs/${op}_"* | head -1)
echo "########## $op | summary ##########" echo "########## $op | summary ##########"
python batch_summary.py --ab "$BEFORE/run.log" "$AFTER/run.log" --label "$op" \ python batch_summary.py --ab "$BEFORE/run.log" "$AFTER/run.log" --label "$op" \
|| echo "!!! summary failed: $op" || echo "!!! summary failed: $op"
done done
echo "########## 筛选汇总 ##########"
awk -F'\t' 'NR>1{printf " %-12s %-22s cvt %s->%s kernels %s/%s rows %s/%s\n", \
$10,$1,$8,$9,$6,$7,$4,$5}' "$TSV" | sort
echo ">>> 只把 IR_CHANGED 的算子送进 ab_repeat.sh。"
echo "########## ALL DONE ##########" echo "########## ALL DONE ##########"
} 2>&1 | tee "$LOG" } 2>&1 | tee "$LOG"
@@ -65,4 +127,5 @@ mkdir -p runs
sed -i -E $'s/\x1b\\[[0-9;]*[A-Za-z]//g' "$LOG" sed -i -E $'s/\x1b\\[[0-9;]*[A-Za-z]//g' "$LOG"
echo ">>> 总 log: $(pwd)/$LOG" echo ">>> 总 log: $(pwd)/$LOG"
echo ">>> 每算子两份 run.log 在 runs/<op>_<时间戳>/ 下,按时间先后即 before / after" echo ">>> 筛选表: $(pwd)/$TSV"
echo ">>> 每算子两份 run.log 在 $ROOT/<op>_{before,after}/ 下"
-103
View File
@@ -1,103 +0,0 @@
#!/bin/bash
# 第一步:筛掉不值得跑 A/B 的算子。
#
# 为什么要单独一步:S5000 实测,筛选一个算子约 0.8 分钟,六轮 A/B 一个算子
# 13~44 分钟——差 20~50 倍。21 个算子筛一遍 15 分钟,挡掉 13 个,把昂贵队列
# 从 7 小时压到 2.7 小时。筛选的全部价值就在这个比例上。
#
# 口径与 A/B 完全一致,因为筛选也是一次 A/B:A 侧 record,B 侧 REPLAY_FROM 它。
# 两侧各自 record 是不行的——各自 sweep 会选出不同 config,生成的 IR 自然不同,
# 于是每个算子都被判成"开关改变了生成代码",筛选等于没筛。这条本仓踩过。
#
# 判据是 ttgir 指纹,不是 convert_layout 计数。cvt 数相同不等于生成代码相同,
# 而 A/B 要测的是"生成代码变了没有"。cvt 数照样记下来,它解释的是变化的性质。
#
# 用法:
# SWITCH=FLAGTREE_MUSA_RLC_ENHANCE OPS="col2im:col2im topk:topk perf_fft:fft" \
# ZL_BACKEND=musa FLAGGEMS_DIR=/path/to/FlagGems bash screen.sh
#
# OPS 元素为 OP[:OP_FILE[:DTYPES]],省略 OP_FILE 时等于 OP。
# DTYPES 留空表示用该算子自己的全套——**不要全局写死一个 dtype**:
# fft 只吃 complex64,被一刀切的 DTYPES=float16 直接判死过一次。
#
# 产物:runs/screen_<开关>_<时间戳>/,含 screen.tsv 与逐算子 run 目录。
set -uo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "./_ir_fingerprint.sh"
: "${SWITCH:?SWITCH is required, e.g. FLAGTREE_MUSA_RLC_ENHANCE}"
OPS="${OPS:-}"
: "${OPS:?OPS is required, e.g. \"topk:topk perf_fft:fft\"}"
CELL_TIMEOUT="${CELL_TIMEOUT:-2400}"
ROOT="runs/screen_${SWITCH}_$(date +%Y%m%d-%H%M%S)"
mkdir -p "$ROOT"
TSV="$ROOT/screen.tsv"
LOG="$ROOT/screen.log"
printf 'op\top_file\tdtypes\trows_a\trows_b\tkernels_a\tkernels_b\tcvt_a\tcvt_b\tverdict\n' > "$TSV"
count_cvt() {
find "$1/ttgir" -name '*.ttgir' -type f -exec cat {} + 2>/dev/null | grep -c convert_layout || true
}
count_kernels() { find "$1/ttgir" -name '*.ttgir' -type f 2>/dev/null | wc -l; }
count_rows() { grep -c '^SUCCESS' "$1/run.log" 2>/dev/null || true; }
{
echo ">>> switch=$SWITCH ops=$OPS"
for entry in $OPS; do
op="${entry%%:*}"; rest="${entry#*:}"
if [[ "$rest" == "$entry" ]]; then op_file="$op"; dtypes=""
else
op_file="${rest%%:*}"; d="${rest#*:}"
[[ "$d" == "$rest" ]] && dtypes="" || dtypes="$d"
fi
echo "########## $op | A ($SWITCH=0, record) ##########"
OP="$op" OP_FILE="$op_file" OUT_DIR="$ROOT/${op}_A" DTYPES="$dtypes" \
timeout -k 30 "$CELL_TIMEOUT" env "$SWITCH=0" bash run_pytest.sh
rc_a=$?
echo "########## $op | B ($SWITCH=1, replay A) ##########"
OP="$op" OP_FILE="$op_file" OUT_DIR="$ROOT/${op}_B" DTYPES="$dtypes" \
REPLAY_FROM="$ROOT/${op}_A" \
timeout -k 30 "$CELL_TIMEOUT" env "$SWITCH=1" bash run_pytest.sh
rc_b=$?
ra=$(count_rows "$ROOT/${op}_A"); rb=$(count_rows "$ROOT/${op}_B")
ka=$(count_kernels "$ROOT/${op}_A"); kb=$(count_kernels "$ROOT/${op}_B")
ca=$(count_cvt "$ROOT/${op}_A"); cb=$(count_cvt "$ROOT/${op}_B")
fa=$(ir_fingerprint "$ROOT/${op}_A") || fa=""
fb=$(ir_fingerprint "$ROOT/${op}_B") || fb=""
if (( rc_a != 0 || rc_b != 0 )) || [[ "${ra:-0}" == 0 || "${rb:-0}" == 0 ]]; then
verdict="NO_ROWS"
note="跑不出测量行(rc=$rc_a/$rc_b)。先看 console,多半是 dtype 不被该算子接受,或该算子在本后端拿不到 config"
elif [[ -z "$fa" || -z "$fb" ]]; then
verdict="NO_KERNEL"
note="没编出 .ttgir:该算子在本后端没走 Triton。编译器开关对它无从谈起——这不是'开关无作用'"
elif [[ "$fa" == "$fb" ]]; then
verdict="NO_IR_DIFF"
note="两侧 ttgir 逐字节相同(config 已锁死):开关没改变该算子的生成代码,跑 A/B 只会测到噪声"
else
verdict="IR_CHANGED"
note="生成代码有变(cvt ${ca}→${cb},指纹 ${fa:0:8}/${fb:0:8}):值得排六轮 A/B"
fi
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$op" "$op_file" "${dtypes:-<all>}" "${ra:-0}" "${rb:-0}" \
"$ka" "$kb" "${ca:-0}" "${cb:-0}" "$verdict" >> "$TSV"
echo ">>> $op: $verdict — $note"
rm -rf "$ROOT/${op}_A/.triton_cache" "$ROOT/${op}_B/.triton_cache"
done
echo "########## 汇总 ##########"
awk -F'\t' 'NR>1{print " " $10 "\t" $1 "\t(cvt " $8 "→" $9 ", kernels " $6 "/" $7 ", rows " $4 "/" $5 ")"}' "$TSV" | sort
echo
echo ">>> 只把 IR_CHANGED 的算子送进 ab_repeat.sh。"
echo ">>> NO_IR_DIFF 跑六轮只会测出噪声并报成一个'无差异'结论。"
echo ">>> NO_KERNEL / NO_ROWS 要先修可运行性,不要当成开关的结论。"
} 2>&1 | tee "$LOG"
sed -i -E $'s/\x1b\\[[0-9;]*[A-Za-z]//g' "$LOG"
echo ">>> 结果表: $(pwd)/$TSV"
echo ">>> 总 log: $(pwd)/$LOG"