Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fb3216a
Add a converter for real ethrex blocks
nicole-graus Jul 27, 2026
a576511
Add default-features=false, make curl download atomic, pin fixture by…
nicole-graus Jul 30, 2026
ef7a4f1
fix missing import, assert the fixture's digest instead of its length…
nicole-graus Jul 30, 2026
99b95ff
Reject caches from unrecognised networks
nicole-graus Jul 30, 2026
5c31a79
Fix test-ethrex-offline to skip both real-block tests, retry the cach…
nicole-graus Jul 30, 2026
9c426b9
Merge branch 'main' into feat/ethrex-real-block
diegokingston Jul 30, 2026
f130dcd
update readme and add test
nicole-graus Jul 31, 2026
27fc1c2
Merge branch 'feat/ethrex-real-block' of github.com:yetanotherco/lamb…
nicole-graus Jul 31, 2026
05e0285
fix(tooling): re-download the ethrex-replay cache when its pinned rev…
MauroToscano Jul 31, 2026
50dfca4
perf(ci): cache the detached ethrex workspaces and drop a duplicate b…
MauroToscano Jul 31, 2026
eea2c37
test(tooling): pin the real block's chain config independently of the…
MauroToscano Jul 31, 2026
da9a82b
bench(ci): make a real Ethereum block the headline benchmark workload
MauroToscano Jul 31, 2026
2332174
build(ethrex): fetch the real-block fixture, and take it out of the P…
MauroToscano Jul 31, 2026
2e0297c
docs(ethrex): keep the block choice out of the wiring, and record mea…
MauroToscano Jul 31, 2026
31fb041
bench(ethrex): repoint the real block to mainnet 25368371
MauroToscano Jul 31, 2026
19ed945
bench(ethrex): repoint the benchmark block to mainnet 25368371
MauroToscano Jul 31, 2026
c084564
bench(gpu): make the real block the default /bench-gpu workload
MauroToscano Jul 31, 2026
111fc1f
docs(ethrex): record /bench-abba's real-block cost and why it stays m…
MauroToscano Jul 31, 2026
fdb92f6
Merge remote-tracking branch 'origin/main' into review/ethrex-real-bl…
MauroToscano Jul 31, 2026
c163766
docs(ethrex): refresh the measured numbers to main vintage
MauroToscano Jul 31, 2026
4ebd836
bench(gpu): default the real-block GPU path to epoch 2^22
MauroToscano Jul 31, 2026
96ee504
bench(cpu): encode the CPU epoch calibration — bench runner to 2^22
MauroToscano Jul 31, 2026
bd60a61
bench(ci): fold the real block into /bench, sampled at 3 runs
MauroToscano Jul 31, 2026
7a29bb1
bench(ci): /bench proves the real block only
MauroToscano Jul 31, 2026
6af5d68
test(bench): commit the PR-comment renderer harness
MauroToscano Jul 31, 2026
2826055
build(tooling): pin k256 features directly instead of promoting the c…
MauroToscano Jul 31, 2026
96079f0
ci(ethrex): name the fixture-test workflow as what it is
MauroToscano Jul 31, 2026
05993ee
refactor(tooling): rename ethrex-real-block to ethrex-block-converter
MauroToscano Jul 31, 2026
3d11034
ci(ethrex): drop the old crate name from the usability screen's cache…
MauroToscano Jul 31, 2026
ac73dda
bench(gpu): /bench-gpu proves the real block, only
MauroToscano Jul 31, 2026
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
112 changes: 47 additions & 65 deletions .github/workflows/benchmark-gpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ name: Benchmark GPU (PR)
# It builds the cli at the PR head and at main, runs N interleaved pairs on the GPU,
# posts the paired-t + Wilcoxon verdict back to the PR, then ALWAYS destroys the box.
#
# Triggered by a "/bench-gpu [N] [cont[TX]|mono[TX]]" comment on a PR (N = pair count,
# default 14) or via workflow_dispatch. Workload default: ethrex 100tx --continuations;
# "mono[TX]" = 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.
# Triggered by a "/bench-gpu [N]" comment on a PR (N = pair count, default 14) or via
# workflow_dispatch.
#
# Workload: the real block (see tooling/ethrex-block-converter/README.md), proven with
# --continuations at the calibrated epoch size below.
# Orchestration runs on a GitHub-hosted runner; all GPU work happens on the rented
# Vast box (provisioned by the template onstart).
#
Expand All @@ -23,9 +24,6 @@ on:
pairs:
description: "Number of A/B/B/A pairs"
default: "14"
mode:
description: "Workload: cont[TX] (--continuations, TX defaults to 100) or mono[TX] (monolithic, TX defaults to 5)"
default: "cont100"
issue_comment:
types: [created]

Expand All @@ -47,6 +45,25 @@ env:
VAST_IMAGE_DISK: "64"
# cli features for the ABBA build — the GPU (cuda) prover path plus jemalloc heap stats.
BENCH_FEATURES: "jemalloc-stats,prover/cuda"
# Continuation epoch for the REAL-BLOCK path, from the RTX 5090 calibration on
# 2026-07-31 against main @9ccdaf2 (raw traces:
# ~/workspace/lambda_vm_bench_cache/gpu_epoch_calib_2026-07-31/, PROVENANCE.txt).
# Measured on the 32,607 MiB card, same fixture and CLI, one prove per setting:
#
# 2^21 70.52 s wall 19,193 MiB VRAM (58.9%) 25 epochs 1.65 GB proof
# 2^22 59.87 s wall 23,193 MiB VRAM (71.1%) 13 epochs 1.12 GB proof
# 2^23 OOM at 32,079 MiB (98.4%) after 9.7 s — needs ~44 GiB
#
# So 2^22 is the largest setting that fits a 32 GiB card, and it is ~15% faster than
# 2^21 (equivalently, 2^21 is ~18% slower) with 28.9% VRAM headroom left. 2^23 is out
# of reach for every card below 48 GiB, not just this one.
#
# 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.
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 }}"
# Pin the Vast CLI to an immutable commit (a PyPI version can be re-published; a commit
Expand All @@ -63,8 +80,8 @@ jobs:
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/bench-gpu') &&
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association))
# Provisioning + dual cuda build (~30 min) + 2*pairs proves (~3.5 min each at
# the default cont100). Sized for the 32-pair worst case (~4.5 hr) with headroom;
# Provisioning + dual cuda build + 2*pairs real-block proves (~2 min each,
# host-CPU-dependent). Sized for the 32-pair worst case with generous headroom;
# teardown still always destroys the box.
timeout-minutes: 330
steps:
Expand All @@ -76,47 +93,30 @@ jobs:
COMMENT_BODY: ${{ github.event.comment.body }}
PR_NUM: ${{ github.event.issue.number }}
DISPATCH_PAIRS: ${{ github.event.inputs.pairs }}
DISPATCH_MODE: ${{ github.event.inputs.mode }}
DISPATCH_REF: ${{ github.ref_name }}
run: |
if [ "$EVENT_NAME" = "issue_comment" ]; then
# Pin the head SHA (works for fork PRs; avoids a force-push race mid-run).
HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$GITHUB_REPOSITORY" --json headRefOid -q .headRefOid)
OUT_PR_NUM="$PR_NUM"; OUT_HEAD_SHA="$HEAD_SHA"; OUT_BRANCH=""
# Everything after "/bench-gpu" on its line, tokens in any order:
# a number = pair count; cont[TX]/mono[TX] = workload (see loop below).
# Everything after "/bench-gpu" on its line: the only token is an
# optional pair count.
ARGS=$(printf '%s' "$COMMENT_BODY" | tr -d '\r' | sed -n 's|^/bench-gpu||p' | head -n1)
PAIRS=14
else
# workflow_dispatch: compare this branch vs main.
OUT_PR_NUM=""; OUT_HEAD_SHA=""; OUT_BRANCH="$DISPATCH_REF"
ARGS="$DISPATCH_MODE"
ARGS=""
PAIRS=${DISPATCH_PAIRS:-14}
fi
# Defaults: continuations with the 100-transfer fixture (production mode).
CONTINUATIONS=1; TX_COUNT=100
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'" ;;
esac
done
# TX_COUNT and PAIRS are interpolated into the remote bash -lc below: enforce
# digits-only. Mono is capped at 5tx (monolithic peak heap grows with the
# trace; 20tx needs ~78 GB, over the 48 GB box floor).
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
# PAIRS is interpolated into the remote bash -lc below: enforce digits-only.
case "$PAIRS" in
''|*[!0-9]*) echo "::warning::invalid pair count '$PAIRS'; using 14"; PAIRS=14 ;;
esac
Expand All @@ -132,39 +132,14 @@ jobs:
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.
WORKLOAD="ethrex real block, continuations"
{
echo "pr_num=$OUT_PR_NUM"
echo "head_sha=$OUT_HEAD_SHA"
echo "branch=$OUT_BRANCH"
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 the
# full dual build (~1 hr of GPU rental). 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
REF="${OUT_HEAD_SHA:-$OUT_BRANCH}"
if SIDE=$(gh api "repos/$GITHUB_REPOSITORY/contents/prover/Cargo.toml?ref=$REF" \
-H "Accept: application/vnd.github.raw" 2>/dev/null) \
&& printf '%s' "$SIDE" | grep -q '^rkyv' \
&& ! printf '%s' "$SIDE" | grep -q pointer_width_64; then
echo "::error::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."
exit 1
fi
fi
echo "Using $PAIRS A/B/B/A pairs on $WORKLOAD"

- name: Acknowledge (react + occupancy notice)
Expand All @@ -182,7 +157,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)';
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. This takes ~2.5 hr at the default workload; the result will replace this comment.`;
// 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;
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,
issue_number: context.issue.number, per_page: 100,
Expand Down Expand Up @@ -234,10 +214,11 @@ jobs:
# because vast can't numerically compare the driver_version string server-side.
MIN_DRIVER: "580"
run: |
# cpu_ram filter is in GB. Floor 48 GB: continuation proves are flat-memory
# (~10 GB) and the legacy 5tx monolithic prove also fits — far below the old
# 20-transfer prove (~78 GB heap) that set the previous 96 GB floor. 48 GB
# widens the dedicated pool (~15 vs ~11 offers).
# cpu_ram filter is in GB. Floor 48 GB: the real block at epoch 2^22 peaks at
# ~36 GB host RSS on the CUDA path (measured, main vintage) — ~25% headroom.
# Continuation peak is set by the epoch size, not the block, so bigger blocks
# don't move it; raising the epoch would (see the calibration tables in
# tooling/ethrex-block-converter/README.md).
# gpu_frac=1 requires a WHOLE-MACHINE offer (you rent every GPU on the host), so
# Vast places no other tenant on the box: CPU cores, RAM/memory bandwidth, PCIe,
# and NVMe are fully dedicated. Without it the "most expensive" sort below lands on
Expand Down Expand Up @@ -364,6 +345,7 @@ jobs:
KEY: ${{ steps.sshkey.outputs.key_path }}
run: |
SSH="ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -o BatchMode=yes -i $KEY -p $PORT root@$HOST"

echo "Waiting for the template onstart script to finish (Rust + LLVM + sysroot + clone)..."
# The bootstrap's final stdout line is "=== done ===". Vast captures onstart
# output to /var/log/onstart.log; fall back to checking the artifacts it leaves.
Expand Down Expand Up @@ -395,8 +377,6 @@ jobs:
HEAD_SHA: ${{ steps.config.outputs.head_sha }}
BRANCH: ${{ steps.config.outputs.branch }}
PAIRS: ${{ steps.config.outputs.pairs }}
CONTINUATIONS: ${{ steps.config.outputs.continuations }}
TX_COUNT: ${{ steps.config.outputs.tx_count }}
run: |
SSH="ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -o BatchMode=yes -i $KEY -p $PORT root@$HOST"

Expand All @@ -421,7 +401,9 @@ jobs:
# explicit and robust to the template default changing.) The harness still builds the
# cli at REF_A (the PR) and origin/main in isolated worktrees, runs PAIRS interleaved
# A/B/B/A proves, and prints the paired-t CI + Wilcoxon verdict. BENCH_FEATURES routes
# the build through the CUDA prover path; CONTINUATIONS/TX_COUNT pick the workload.
# the build through the CUDA prover path. bench_abba.sh is shared with the CPU ABBA
# flow and has its own defaults, so its knobs are pinned explicitly here: the real
# block, continuations, the calibrated epoch.
# The harness runs from main, so workflow/script changes take effect post-merge.
# REBUILD=1: each Vast box is fresh, GPU-specific hardware — always rebuild both
# binaries (cubin is compiled for the detected arch); never trust a cached binary.
Expand All @@ -437,7 +419,7 @@ jobs:
git fetch --force origin main; $FETCH; \
git checkout -f origin/main; \
REBUILD=1 CUDARC_PIN=cuda-12080 SYSROOT_DIR=/opt/lambda-vm-sysroot BENCH_FEATURES='$BENCH_FEATURES' \
CONTINUATIONS=$CONTINUATIONS TX_COUNT=$TX_COUNT \
WORKLOAD=real CONTINUATIONS=1 EPOCH_SIZE_LOG2=$GPU_REAL_EPOCH_LOG2 \
scripts/bench_abba.sh $REF_A origin/main $PAIRS"

# pipefail so a failed remote bench (e.g. a prove that dies) propagates through the
Expand Down
Loading
Loading