Skip to content

feat(stark): opt-in LogUp-GKR mode — batch GKR replaces committed LogUp columns (port of #485) #184

feat(stark): opt-in LogUp-GKR mode — batch GKR replaces committed LogUp columns (port of #485)

feat(stark): opt-in LogUp-GKR mode — batch GKR replaces committed LogUp columns (port of #485) #184

Workflow file for this run

name: Bench verifier
# 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]
concurrency:
# Serialization is provided by the single self-hosted bench runner (jobs queue
# for it). Never cancel a running bench: real /bench-verify comments share the
# per-PR group and queue; any other comment (this workflow fires on every
# issue_comment) gets a unique throwaway group so it can't sit in — or evict —
# the real queue.
group: ${{ startsWith(github.event.comment.body, '/bench-verify') && format('bench-verify-{0}', github.event.issue.number) || format('bench-verify-ignore-{0}', github.run_id) }}
cancel-in-progress: false
permissions:
contents: read
pull-requests: write
issues: write
jobs:
verify:
if: >-
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. 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: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner, repo: context.repo.repo,
comment_id: context.payload.comment.id, content: 'eyes'
});
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 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
id: cfg
env:
GH_TOKEN: ${{ github.token }}
PR_NUM: ${{ github.event.issue.number }}
COMMENT_BODY: ${{ github.event.comment.body }}
DISPATCH_PAIRS: ${{ github.event.inputs.pairs }}
run: |
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"
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
echo "pairs=$N" >> "$GITHUB_OUTPUT"
- name: Checkout (full history for ref resolution)
uses: actions/checkout@v4
with:
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
- name: Add cargo to PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Run verifier benchmark
id: run
env:
HEAD_SHA: ${{ steps.cfg.outputs.head_sha }}
PAIRS: ${{ steps.cfg.outputs.pairs }}
run: |
export SYSROOT_DIR="$HOME/.lambda-vm-sysroot"
set -o pipefail
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), 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 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 }}
# Share build-std and the host cargo target across ref worktrees, rooted under
# the script's /tmp cache dir: cuts cold build time and per-worktree disk.
GUEST_TARGET_DIR: /tmp/recursion_cycles_run/shared_guest_target
HOST_TARGET_DIR: /tmp/recursion_cycles_run/shared_host_target
run: |
export SYSROOT_DIR="$HOME/.lambda-vm-sysroot"
.github/scripts/run_recursion_bench.sh "$HEAD_SHA"
- name: Post result
if: always()
uses: actions/github-script@v7
env:
HEAD_SHA: ${{ steps.cfg.outputs.head_sha }}
PAIRS: ${{ steps.cfg.outputs.pairs }}
OUTCOME: ${{ steps.run.outcome }}
RECURSION_OUTCOME: ${{ steps.recursion.outcome }}
with:
script: |
const fs = require('fs');
const read = (p) => { try { return fs.readFileSync(p, 'utf8').trim(); } catch { return ''; } };
// Bound any raw-log fallback so a future header rename (empty *_result.txt)
// can't dump the entire build log into the PR comment.
const tail = (s, n) => s.split('\n').slice(-n).join('\n');
const head = (process.env.HEAD_SHA || '').slice(0, 10), pairs = process.env.PAIRS;
let body = `## Verifier benchmark — \`${head}\` vs \`main\` (${pairs} pairs)\n\n`;
if (process.env.OUTCOME === 'success') {
const res = read('/tmp/verify_result.txt') || tail(read('/tmp/verify_out.txt'), 30);
body += res + '\n';
body += '\n<sub>Drift-free interleaved A/B/B/A measurement. - = PR faster. ';
body += 'Trust the verdict when paired-t and Wilcoxon agree.</sub>\n';
} else {
body += `❌ Run failed. Last log lines:\n\n` + '```\n' + tail(read('/tmp/verify_out.txt'), 30) + '\n```\n';
}
// Additive recursion-guest cycle section, kept clearly separated from the
// verifier verdict above so a failure here can't change how the bench reads.
body += '\n---\n\n## Recursion guest cycles (main vs PR)\n\n';
if (process.env.RECURSION_OUTCOME === 'success') {
const rec = read('/tmp/recursion_result.txt') || tail(read('/tmp/recursion_out.txt'), 20);
if (rec) {
body += rec + '\n';
} else {
body += '_No recursion comparison output was captured._\n';
}
} else {
const rtail = tail(read('/tmp/recursion_out.txt'), 20);
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,
});
const existing = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Verifier benchmark —'));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner, repo: context.repo.repo,
comment_id: existing.id, body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
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