Skip to content
Merged
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
6 changes: 1 addition & 5 deletions .github/scripts/aggregate_recursion_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,6 @@ def parse(text):
return total_cycles, unique_pcs, exec_time, tables


def short(name, width=90):
return name if len(name) <= width else name[: width - 1] + "…"


def render_table(rows, denom_label):
if not rows:
return "> _no rows_\n"
Expand All @@ -105,7 +101,7 @@ def render_table(rows, denom_label):
for i, r in enumerate(rows, 1):
body += (
f"| {i} | {r['cycles']:,} | {r['pct']}% | {r['cum']}% | "
f"{r['pcs']} | `{short(r['fn'])}` |\n"
f"{r['pcs']} | `{r['fn']}` |\n"
)
last_cum = rows[-1]["cum"]
body += (
Expand Down
45 changes: 45 additions & 0 deletions .github/scripts/run_recursion_bench.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
#
# Runs scripts/bench_recursion_cycles.sh across every preset regime (min,
# blowup2/blowup4, blowup4-block) and appends each result — or an
# "unavailable" note when the ref/preset combo isn't supported — to
# /tmp/recursion_result.txt for the bench-verify.yml PR comment.
#
# Usage: .github/scripts/run_recursion_bench.sh HEAD_SHA
set -euo pipefail

HEAD_SHA="$1"
RESULT=/tmp/recursion_result.txt
: > "$RESULT"

run_preset() {
local preset="$1"
local log="/tmp/recursion_out_${preset}.txt"
if scripts/bench_recursion_cycles.sh "$HEAD_SHA" origin/main "$preset" 2>&1 | tee "$log"; then
{ echo; sed -n '/=== Recursion-guest cycle/,$p' "$log"; } >> "$RESULT"
else
{ echo; echo "_(${preset} regime unavailable for these refs — see the workflow log.)_"; } >> "$RESULT"
fi
}

run_preset min
# Post-result's raw-log fallback reads /tmp/recursion_out.txt (unsuffixed).
cp -f /tmp/recursion_out_min.txt /tmp/recursion_out.txt

# blowup2/blowup4: full-query base-layer regimes over the `empty` diagnostic
# program. Need origin/main's RECURSION_DUMP_PRESET support to dump a
# non-min blob; checked once up front instead of failing each preset in turn.
if git grep -q RECURSION_DUMP_PRESET origin/main -- prover/src/tests/ 2>/dev/null; then
run_preset blowup2
run_preset blowup4
else
{ echo; echo "_(blowup2/blowup4 full-query regimes need \`origin/main\` to have the preset-aware dump test (RECURSION_DUMP_PRESET) — not merged yet, so only \`min\` is compared for this PR.)_"; } >> "$RESULT"
fi

# blowup4-block: same blowup=4 verifier over a REAL ethrex block (via the
# `continuation` guest). Needs origin/main's RECURSION_DUMP_EPOCH_LOG2 support.
if git grep -q RECURSION_DUMP_EPOCH_LOG2 origin/main -- prover/src/tests/ 2>/dev/null; then
run_preset blowup4-block
else
{ echo; echo "_(blowup4-block real-ethrex-block regime needs \`origin/main\` to support RECURSION_DUMP_EPOCH_LOG2 — not merged yet.)_"; } >> "$RESULT"
fi
95 changes: 60 additions & 35 deletions .github/workflows/bench-verify.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
name: Bench verifier

# Manual-only (/bench-verify); separate from /bench so they never share the bench server.
# Manual-only (/bench-verify, or workflow_dispatch to test workflow changes on a
# branch — issue_comment always runs main's copy of this file, see profile-recursion.yml);
# separate from /bench so they never share the bench server.
on:
workflow_dispatch:
inputs:
pairs:
description: "ABBA pair count (2-40)"
required: false
default: "20"
issue_comment:
types: [created]

Expand All @@ -22,20 +30,19 @@ permissions:
jobs:
verify:
if: >-
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/bench-verify') &&
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/bench-verify') &&
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association))
runs-on: [self-hosted, bench]
# Job cap. The verifier bench is ~5-6 min and the recursion measurement itself ~1 min,
# but on a cold runner the recursion BUILDS dominate: MEASURE_CLI (release cli) once,
# plus PER REF a guest build (~10-20 min) and a prover-test build for the blob dump.
# All cached in /tmp for later runs, and build-std / the host cargo target are shared
# across ref worktrees (see the recursion step's env) to keep the cold run under this
# cap. The recursion step also has its own tighter timeout so a runaway build there
# can't burn the whole job and lose the already-captured verifier result.
# Job cap. On a cold runner the recursion BUILDS dominate: MEASURE_CLI once, plus
# per ref a guest build and a prover-test build. Cached in /tmp; build-std / host
# cargo target shared across ref worktrees (see the recursion step's env).
timeout-minutes: 90
steps:
- name: Acknowledge (react + occupancy notice)
if: github.event_name == 'issue_comment'
uses: actions/github-script@v7
with:
script: |
Expand All @@ -46,7 +53,7 @@ jobs:
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number,
body: '⏳ **Benchmark started** on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes.'
body: '⏳ **Benchmark started** on the bench server. The recursion-guest cycle comparison adds guest builds on top of the verifier bench, longer on a cold runner. The bench server is occupied until it finishes.'
});

- name: Resolve PR head + pair count
Expand All @@ -55,14 +62,25 @@ jobs:
GH_TOKEN: ${{ github.token }}
PR_NUM: ${{ github.event.issue.number }}
COMMENT_BODY: ${{ github.event.comment.body }}
DISPATCH_PAIRS: ${{ github.event.inputs.pairs }}
run: |
# Head SHA (not branch name) so fork PRs resolve and a mid-run force-push can't race.
HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$GITHUB_REPOSITORY" --json headRefOid -q .headRefOid)
if [ "$GITHUB_EVENT_NAME" = workflow_dispatch ]; then
# Testing this workflow's own changes: bench the dispatched branch vs main.
HEAD_SHA="$GITHUB_SHA"
N="${DISPATCH_PAIRS:-20}"
else
# Head SHA (not branch name) so fork PRs resolve and a mid-run force-push can't race.
HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$GITHUB_REPOSITORY" --json headRefOid -q .headRefOid)
# Optional pair count "/bench-verify 32"; default 20.
N=$(echo "$COMMENT_BODY" | sed -n 's|^/bench-verify[[:space:]]*\([0-9]\+\).*|\1|p')
N=${N:-20}
fi
echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
# Optional pair count "/bench-verify 32"; default 20, clamp [2,40].
N=$(echo "$COMMENT_BODY" | sed -n 's|^/bench-verify[[:space:]]*\([0-9]\+\).*|\1|p')
N=${N:-20}
if [ "$N" -lt 2 ] 2>/dev/null || [ "$N" -gt 40 ] 2>/dev/null; then
if ! [[ "$N" =~ ^[0-9]+$ ]]; then
echo "::warning::pair count '$N' is not a number; using 20"
N=20
fi
if [ "$N" -lt 2 ] || [ "$N" -gt 40 ]; then
echo "::warning::pair count $N out of range [2,40]; using 20"
N=20
fi
Expand All @@ -74,6 +92,7 @@ jobs:
fetch-depth: 0

- name: Fetch PR head commit (works for fork PRs)
if: github.event_name == 'issue_comment'
env:
PR_NUM: ${{ github.event.issue.number }}
run: git fetch origin "pull/$PR_NUM/head" --quiet
Expand All @@ -92,24 +111,20 @@ jobs:
scripts/bench_verify.sh "$HEAD_SHA" origin/main "$PAIRS" 2>&1 | tee /tmp/verify_out.txt
sed -n '/=== Verify ABBA result/,$p' /tmp/verify_out.txt > /tmp/verify_result.txt

# Additive: deterministic recursion-guest cycle+accelerator diff (PR vs main).
# The measurement is one exact `execute --cycles` reading per ref (~1 min total, no
# ABBA). Cold wall time is dominated by BUILDS: MEASURE_CLI (release cli) once, plus
# per ref a guest build (~10-20 min) and a prover-test build for the blob dump; the
# GUEST_TARGET_DIR / HOST_TARGET_DIR below share build-std and the host cargo target
# across the two ref worktrees so the second ref is much cheaper (and per-worktree
# disk shrinks). continue-on-error + `!cancelled()` keep this fully isolated from the
# verifier bench above: a failure OR timeout here never fails the job nor clobbers the
# verifier verdict, and it still runs if the verifier step failed.
# Additive: deterministic recursion-guest cycle+accelerator diff (PR vs main), in
# four regimes: `min`, `blowup2`, `blowup4` (empty diagnostic inner program) plus
# `blowup4-block` (same verifier over a REAL ethrex block, via the `continuation`
# guest). One exact `execute --cycles` reading per ref (no ABBA); blowup4-block's
# dumped blob is cached by ref SHA (bench_recursion_cycles.sh), so a repeat run
# skips re-proving. GUEST_TARGET_DIR / HOST_TARGET_DIR share build-std and the
# host cargo target across ref worktrees. continue-on-error + `!cancelled()`
# isolate this from the verifier bench above.
- name: Run recursion guest cycle benchmark
id: recursion
if: ${{ !cancelled() }}
continue-on-error: true
# Fail-fast well under the 90-min job cap so a runaway recursion build can't burn
# the whole job and lose the already-captured verifier result (continue-on-error
# absorbs the timeout; the job still posts the verifier verdict). Sized with
# headroom over a cold shared-build run (MEASURE_CLI + 2 guest builds + 2 blob-dump
# builds).
# Fail-fast under the job cap so a runaway build can't burn the whole job
# (continue-on-error absorbs the timeout; the verifier verdict still posts).
timeout-minutes: 70
env:
HEAD_SHA: ${{ steps.cfg.outputs.head_sha }}
Expand All @@ -119,9 +134,7 @@ jobs:
HOST_TARGET_DIR: /tmp/recursion_cycles_run/shared_host_target
run: |
export SYSROOT_DIR="$HOME/.lambda-vm-sysroot"
set -o pipefail
scripts/bench_recursion_cycles.sh "$HEAD_SHA" origin/main min 2>&1 | tee /tmp/recursion_out.txt
sed -n '/=== Recursion-guest cycle/,$p' /tmp/recursion_out.txt > /tmp/recursion_result.txt
.github/scripts/run_recursion_bench.sh "$HEAD_SHA"

- name: Post result
if: always()
Expand Down Expand Up @@ -163,6 +176,11 @@ jobs:
body += '⚠️ Recursion cycle bench did not complete (does not affect the verifier verdict above).';
body += rtail ? ' Last log lines:\n\n' + '```\n' + rtail + '\n```\n' : '\n';
}
// workflow_dispatch has no PR to comment on; write to the job summary instead.
if (context.eventName !== 'issue_comment') {
await core.summary.addRaw(body).write();
return;
}
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number,
Expand All @@ -180,3 +198,10 @@ jobs:
issue_number: context.issue.number, body
});
}

# continue-on-error above protects the posted verifier result, not the failure itself.
- name: Fail if recursion cycle bench didn't complete
if: always() && steps.recursion.outcome != 'success'
run: |
echo "::error::Recursion cycle bench step did not complete (outcome=${{ steps.recursion.outcome }}) — see its log and the posted result above."
exit 1
8 changes: 6 additions & 2 deletions .github/workflows/profile-recursion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ jobs:
- name: multi-query
test: multi
title: "Multi query (blowup=8, 128-bit)"
- name: block
test: block
title: "Real ethrex block, 4 transfers (blowup=4, 110 queries)"
steps:
- name: React to comment
if: github.event_name == 'issue_comment' && matrix.name == 'single-query'
Expand Down Expand Up @@ -136,9 +139,10 @@ jobs:
{
echo "## Recursion guest profile"
echo
# Single-query first, then multi-query, then any others.
# Single-query first, then multi-query, then the real-block profile.
for frag in fragments/fragment-single-query.md \
fragments/fragment-multi-query.md; do
fragments/fragment-multi-query.md \
fragments/fragment-block.md; do
[ -f "$frag" ] && { cat "$frag"; echo; }
done
echo "<sub>Commit: ${COMMIT_SHA:0:8} · Runner: self-hosted bench</sub>"
Expand Down
Loading
Loading