Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 53 additions & 65 deletions .github/workflows/bench-abba.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
name: Bench ABBA tiebreaker

# Drift-free paired (A/B/B/A) prover benchmark for resolving small (~1%) deltas the
# cheap PR benchmark can't confirm. At the default workload it OCCUPIES THE SINGLE
# BENCH SERVER FOR SEVERAL HOURS, so it NEVER auto-triggers -- it runs only on an
# explicit `/bench-abba` comment on a PR.
# Drift-free paired (A/B/B/A) prover benchmark for resolving the small deltas /bench
# cannot confirm. It proves the SAME real block /bench does, so a 3-10% reading there
# escalates to a paired test of the same workload rather than a different one. It
# OCCUPIES THE SINGLE BENCH SERVER FOR OVER AN HOUR, so it NEVER auto-triggers -- it
# runs only on an explicit `/bench-abba` comment on a PR.
#
# Syntax: "/bench-abba [N] [cont[TX]|mono[TX]]" (default: 20 pairs, ethrex 100tx
# --continuations; "cont10" is the quick coarse option, "mono[TX]" the legacy
# monolithic prove). Cont proofs need rkyv pointer_width_64 on both sides, so PR
# branches older than that fix must rebase before a cont bench.
# Syntax: "/bench-abba [N]", where N is the pair count (default 12).
#
# Resolution, from the paired t-test (resolvable 95% delta = t* x sd / sqrt(N)). The
# pair-delta sd on this runner is NOT yet measured; the two columns bracket it between
# 1.0% (the GPU box's measured 0.64% plus margin) and 2.0% (sqrt(2) x this runner's
# measured 1.43% single-run CV):
#
# pairs wall resolves (sd 2.0% / sd 1.0%)
# 8 ~50 min 1.7% / 0.8%
# 12 ~72 min 1.3% / 0.6% <- default
# 20 ~1h55m 0.9% / 0.5%
# 32 ~3h 0.7% / 0.4%
#
# Wall is 2 x 158.8 s per pair (the runner's measured prove at epoch 2^22) plus ~8 min
# of setup. The FIRST run measures the sd — it is the `sd` field of the paired-t line
# in the result comment — and this table should be re-pinned to it then.
on:
issue_comment:
types: [created]

env:
# The epoch /bench proves this block at, so the tiebreaker resolves the same
# configuration and not just the same block. Memory picks it: this runner peaks at
# ~52 GB here against its >=64 GiB floor. See benchmark-pr.yml's REAL_BLOCK_EPOCH_LOG2.
ABBA_REAL_EPOCH_LOG2: "22"

concurrency:
# See bench-verify.yml. The single self-hosted bench runner serializes across PRs.
group: ${{ startsWith(github.event.comment.body, '/bench-abba') && format('bench-abba-{0}', github.event.issue.number) || format('bench-abba-ignore-{0}', github.run_id) }}
Expand All @@ -31,9 +50,10 @@ jobs:
startsWith(github.event.comment.body, '/bench-abba') &&
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)
runs-on: [self-hosted, bench]
# Hang guardrail, not expected duration: a cont100 CPU prove is ~7 min, so the
# default 20 pairs runs ~4.5-5 hr and the 40-pair clamp ~9.5 hr, plus builds.
timeout-minutes: 720
# Hang guardrail, not expected duration: a real-block pair is 2 x 158.8 s = ~5.3 min,
# so the default 12 pairs runs ~72 min and the 40-pair clamp ~3.7 hr, plus up to
# ~30 min of two-sided build on a cold cache.
timeout-minutes: 360
steps:
- name: Resolve PR head + bench config
id: cfg
Expand All @@ -50,76 +70,38 @@ jobs:
# force-push race mid-run.
HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$GITHUB_REPOSITORY" --json headRefOid -q .headRefOid)
echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
# Everything after "/bench-abba" on its line, tokens in any order: a number =
# pair count; cont[TX]/mono[TX] = workload.
# Everything after "/bench-abba" on its line. The only token is an optional
# pair count; the workload is fixed to the real block.
ARGS=$(printf '%s' "$COMMENT_BODY" | tr -d '\r' | sed -n 's|^/bench-abba||p' | head -n1)
PAIRS=20; CONTINUATIONS=1; TX_COUNT=100
PAIRS=12
set -f # tokens must not glob-expand against the runner's CWD
for tok in $ARGS; do
case "$tok" in
cont) CONTINUATIONS=1; TX_COUNT=100 ;;
mono) CONTINUATIONS=0; TX_COUNT=5 ;;
cont[0-9]*) CONTINUATIONS=1; TX_COUNT="${tok#cont}" ;;
mono[0-9]*) CONTINUATIONS=0; TX_COUNT="${tok#mono}" ;;
[0-9]*) PAIRS="$tok" ;;
*) echo "::warning::ignoring unrecognized token '$tok'" ;;
[0-9]*) PAIRS="$tok" ;;
*) echo "::warning::ignoring unrecognized token '$tok'" ;;
esac
done
# Digits-only + clamps: PAIRS capped so one comment can't monopolize the
# single bench server; mono capped at 5tx (monolithic peak heap grows with
# the trace; 20tx needs ~78 GB).
if [ "$CONTINUATIONS" = "1" ]; then TX_DEFAULT=100; TX_MAX=100; else TX_DEFAULT=5; TX_MAX=5; fi
case "$TX_COUNT" in
''|*[!0-9]*) echo "::warning::invalid tx count '$TX_COUNT'; using $TX_DEFAULT"; TX_COUNT=$TX_DEFAULT ;;
esac
if [ "$TX_COUNT" -lt 1 ] || [ "$TX_COUNT" -gt "$TX_MAX" ]; then
echo "::warning::tx count $TX_COUNT out of range [1,$TX_MAX] for this mode; using $TX_DEFAULT"
TX_COUNT=$TX_DEFAULT
fi
# Digits-only + clamp, so one comment cannot monopolize the single bench
# server: 40 pairs is already ~3.7 hr of it.
case "$PAIRS" in
''|*[!0-9]*) echo "::warning::invalid pair count '$PAIRS'; using 20"; PAIRS=20 ;;
''|*[!0-9]*) echo "::warning::invalid pair count '$PAIRS'; using 12"; PAIRS=12 ;;
esac
if [ "$PAIRS" -lt 2 ] || [ "$PAIRS" -gt 40 ]; then
echo "::warning::pair count $PAIRS out of range [2,40]; using 20"
PAIRS=20
echo "::warning::pair count $PAIRS out of range [2,40]; using 12"
PAIRS=12
fi
# Even is ideal so the AB/BA orders balance; round an odd request up by one.
if [ "$((PAIRS % 2))" -ne 0 ]; then
PAIRS=$((PAIRS + 1))
echo "::notice::rounded odd pair count up to $PAIRS so AB/BA orders balance"
fi
if [ "$CONTINUATIONS" = "1" ]; then
WORKLOAD="ethrex ${TX_COUNT}tx continuations"
else
WORKLOAD="ethrex ${TX_COUNT}tx monolithic"
fi
# Outputs land before the fail-fast below so the always() result
# comment is fully labeled even when this step exits early.
# The block's identity lives in the Makefile; this job never names one.
WORKLOAD="ethrex real block, continuations"
{
echo "pairs=$PAIRS"
echo "continuations=$CONTINUATIONS"
echo "tx_count=$TX_COUNT"
echo "workload=$WORKLOAD"
} >> "$GITHUB_OUTPUT"
# Fail fast if the PR side predates the pointer_width_64 fix: a >=40tx
# continuation proof exceeds rkyv's old 2 GiB cap and only dies after
# blocking the single bench server for hours. Skip the check when the
# fetch fails: gh api prints HTTP error bodies to stdout, so gate on
# its exit status AND on the payload looking like the manifest (it
# declares rkyv) — never treat an error blob as a missing feature.
# Purely textual; deletable once every open branch postdates the fix.
if [ "$CONTINUATIONS" = "1" ] && [ "$TX_COUNT" -ge 40 ]; then
if SIDE=$(gh api "repos/$GITHUB_REPOSITORY/contents/prover/Cargo.toml?ref=$HEAD_SHA" \
-H "Accept: application/vnd.github.raw" 2>/dev/null) \
&& printf '%s' "$SIDE" | grep -q '^rkyv' \
&& ! printf '%s' "$SIDE" | grep -q pointer_width_64; then
MSG="PR branch predates the rkyv pointer_width_64 fix — a ${TX_COUNT}tx continuation proof cannot serialize. Rebase onto main, or bench with cont10/mono."
echo "$MSG" > /tmp/abba_out.txt # surfaces in the result comment
echo "::error::$MSG"
exit 1
fi
fi
echo "Using $PAIRS A/B/B/A pairs on $WORKLOAD"
echo "Using $PAIRS A/B/B/A pairs on $WORKLOAD at epoch 2^$ABBA_REAL_EPOCH_LOG2"

- name: Acknowledge (react + occupancy notice)
uses: actions/github-script@v7
Expand All @@ -135,7 +117,9 @@ jobs:
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number,
body: `⏳ **ABBA tiebreaker started** on the bench server: ${process.env.PAIRS} pairs of ${process.env.WORKLOAD} (a cont100 pair is ~15 min, so the default 20 pairs runs ~4.5-5 hr; pass a smaller pair count or \`cont10\` for a quicker, coarser run). The bench server is occupied until it finishes.`
// A pair is TWO proves at the runner's measured 158.8 s, so ~5.3 min/pair,
// plus ~8 min of checkout, two-sided build and fixture fetch.
body: `⏳ **ABBA tiebreaker started** on the bench server: ${process.env.PAIRS} pairs of ${process.env.WORKLOAD} — a pair is 2 proves at ~158.8 s, so roughly ${Math.round(8 + Number(process.env.PAIRS) * 5.3)} min. Pass a smaller pair count for a quicker, coarser run. The bench server is occupied until it finishes.`
});

- name: Checkout (full history for ref resolution)
Expand All @@ -156,8 +140,12 @@ jobs:
env:
HEAD_SHA: ${{ steps.cfg.outputs.head_sha }}
PAIRS: ${{ steps.cfg.outputs.pairs }}
CONTINUATIONS: ${{ steps.cfg.outputs.continuations }}
TX_COUNT: ${{ steps.cfg.outputs.tx_count }}
# WORKLOAD=real is bench_abba.sh's own default; pinned here so the workload
# this job measures is stated in the job rather than inherited. The epoch is
# NOT the script's default (2^20, laptop-safe) — it is the runner's tier, the
# same one /bench proves at.
WORKLOAD: real
EPOCH_SIZE_LOG2: ${{ env.ABBA_REAL_EPOCH_LOG2 }}
run: |
export SYSROOT_DIR="$HOME/.lambda-vm-sysroot"
set -o pipefail
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/bench-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ jobs:
env:
HEAD_SHA: ${{ steps.cfg.outputs.head_sha }}
PAIRS: ${{ steps.cfg.outputs.pairs }}
# The script defaults to the real block; this job pins the synthetic one.
# /bench-verify reports TWO arms and the real path runs only the continuation
# one (a real block does not fit monolithically), so inheriting the default
# would silently drop an arm and break comparability with every verify number
# recorded so far.
WORKLOAD: synthetic
run: |
export SYSROOT_DIR="$HOME/.lambda-vm-sysroot"
set -o pipefail
Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/benchmark-gpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ env:
#
# GPU PATH ONLY, and deliberately so. It is NOT pushed into bench_abba.sh's default
# (2^20, which the CPU /bench-abba uses) nor into the CLI's
# DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2 (also 20): 2^22 needs ~28 GiB of HOST memory on
# a CPU build, which would break laptops. VRAM is the binding constraint here and host
# RAM is the binding constraint there, so the two defaults are not the same question.
# DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2 (also 20): 2^22 needs ~32 GiB of HOST memory on
# a CPU build (measured peak RSS on the 124 GiB calibration box; the CUDA path's host
# peak is a different number, ~36 GB — see the cpu_ram floor below), which would break
# laptops. VRAM is the binding constraint here and host RAM is the binding constraint
# there, so the two defaults are not the same question.
GPU_REAL_EPOCH_LOG2: "22"
# Unique per-run label set on the instance, for easy identification in the Vast console.
RUN_LABEL: "gpu-bench-${{ github.run_id }}-${{ github.run_attempt }}"
Expand Down Expand Up @@ -157,11 +159,12 @@ jobs:
// Post the "started" notice under the SAME marker the result step uses, so the
// result updates this comment in place (and re-runs reuse it rather than stacking).
const marker = 'GPU Benchmark (ABBA)';
// Reference: 4 pairs measured ~20 min end-to-end (rental + dual build +
// 8 proves). Per-prove wall varies with the rented host's CPU (the prover
// is partly host-CPU-bound), so scale expectations from pair count, not
// from a fixed per-run figure.
const mins = 12 + Number(process.env.PAIRS) * 2;
// Reference: 4 pairs measured 20 min 11 s end-to-end — 3 min 56 s of rental,
// checkout and dual cuda build, then 4.06 min per pair, since a pair is TWO
// proves at ~2 min each. Per-prove wall varies with the rented host's CPU
// (the prover is partly host-CPU-bound), so the slope is the measured one
// and the intercept carries slack for a colder box.
const mins = 12 + Number(process.env.PAIRS) * 4;
const body = `## GPU Benchmark (ABBA) — running…\n\n⏳ Renting an RTX 5090 on Vast.ai and running ${process.env.PAIRS} interleaved pairs (PR vs main) of ${process.env.WORKLOAD} on the CUDA prover path. Rough ETA ~${mins} min. The result will replace this comment.`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner, repo: context.repo.repo,
Expand Down
Loading
Loading