mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 11:43:20 +00:00
145 lines
7.2 KiB
Bash
Executable File
145 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Auto-select the FASTEST optimization flags for the MAIN C runner on THIS
|
|
# machine, then install the winner as build/runners/c/equix_runner (the path the
|
|
# `equix-c` adapter uses). So the main benchmark always runs the fastest build
|
|
# this host can produce, rather than a hard-coded guess.
|
|
#
|
|
# It builds a few candidate flag sets with the default C compiler, benchmarks the
|
|
# JIT solve path (which dominates PoW cost) with enough reps for a stable median,
|
|
# ranks them, and copies the fastest binary over the main runner. Each rep solves
|
|
# a FRESH challenge derived from the seed (a SHA-256 chain), so tuning spans the
|
|
# same varied-challenge distribution the main solve benchmark measures — not one
|
|
# fixed challenge. The seed is deterministic, so every candidate sees the IDENTICAL
|
|
# challenge stream, keeping the flag comparison a fair apples-to-apples A/B.
|
|
# Idempotent: candidate build dirs are reused, so re-runs are cheap.
|
|
#
|
|
# Tunables (env):
|
|
# CC compiler to tune (default: cc)
|
|
# EQUIX_AUTOTUNE_REPS timed solves per candidate (default: 1000)
|
|
# EQUIX_AUTOTUNE_WARMUP warmup solves per candidate (default: 8)
|
|
# EQUIX_AUTOTUNE_SEED challenge SEED hex (default: deadbeef); each rep
|
|
# solves the next SHA-256-derived challenge in the chain
|
|
# EQUIX_AUTOTUNE_EPSILON if plain -O3 is within this fraction of the best,
|
|
# prefer -O3 (portable, no native/LTO lock-in on a
|
|
# sub-noise win). Default: 0.01 (1%). Set 0 for strict.
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
# Ctrl+C mid-tune: exit cleanly (130) instead of a bare SIGINT kill. Nothing to
|
|
# roll back — candidate build dirs are reused and the main runner is only ever
|
|
# replaced by the atomic mv at the very end, so an interrupt leaves it untouched.
|
|
trap 'echo; echo "autotune: interrupted (Ctrl+C); main runner left unchanged." >&2; exit 130' INT
|
|
|
|
CC="${CC:-cc}"
|
|
REPS="${EQUIX_AUTOTUNE_REPS:-1000}"
|
|
WARMUP="${EQUIX_AUTOTUNE_WARMUP:-8}"
|
|
# Accept the legacy EQUIX_AUTOTUNE_CHALLENGE name as a fallback for the seed.
|
|
SEED="${EQUIX_AUTOTUNE_SEED:-${EQUIX_AUTOTUNE_CHALLENGE:-deadbeef}}"
|
|
EPS="${EQUIX_AUTOTUNE_EPSILON:-0.01}"
|
|
NPROC="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
|
|
EQUIX_COMMIT="$(git -C vendored/equix rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
|
|
|
command -v "$CC" >/dev/null 2>&1 || { echo "autotune: compiler '$CC' not found"; exit 1; }
|
|
command -v python3 >/dev/null 2>&1 || { echo "autotune: python3 required"; exit 1; }
|
|
|
|
# Candidate flag sets on the default compiler. -O0/-O1 are excluded on purpose
|
|
# (the flag sweep shows ~2x regression); these are the fast-tier contenders.
|
|
CANDIDATES=(
|
|
"o2|-O2 -DNDEBUG"
|
|
"o3|-O3 -DNDEBUG"
|
|
"o3-native|-O3 -march=native -DNDEBUG"
|
|
"o3-lto|-O3 -flto -DNDEBUG"
|
|
)
|
|
|
|
# Median solve wall-time (ns) for a runner binary, or non-zero exit if unusable.
|
|
# NB: the Python script is passed via -c so the runner's piped JSON reaches its
|
|
# stdin (a heredoc would be consumed as the script instead).
|
|
measure() {
|
|
printf '{"schema_version":1,"operation":"solve","runtime":"try-compile","repetitions":%d,"warmup":%d,"challenge_seed_hex":"%s"}' \
|
|
"$REPS" "$WARMUP" "$SEED" | "$1" 2>/dev/null | python3 -c '
|
|
import sys, json, statistics
|
|
try:
|
|
d = json.loads(sys.stdin.read().splitlines()[-1])
|
|
assert d.get("ok") and d.get("runs")
|
|
runs = d["runs"]
|
|
walls = [r["wall_ns"] for r in runs if r.get("wall_ns", 0) > 0]
|
|
# Sanity: every rep must be validly timed, and the build must really solve
|
|
# SOMETHING across the stream. Under the varied-challenge seed, individual
|
|
# derived challenges legitimately yield 0 solutions (~17% of them), so the
|
|
# guard is total-solutions>0, not the old per-rep all(>0) which this breaks.
|
|
assert len(walls) == len(runs)
|
|
assert sum(r.get("solutions", 0) for r in runs) > 0
|
|
print(int(statistics.median(walls)))
|
|
except BaseException: # incl. KeyboardInterrupt on Ctrl+C: exit quietly, no stray traceback
|
|
sys.exit(1)
|
|
'
|
|
}
|
|
|
|
# Parallel indexed arrays (macOS ships bash 3.2, which has no associative arrays).
|
|
echo "autotune: compiler=$CC reps=$REPS warmup=$WARMUP seed=$SEED (challenge varied per rep)"
|
|
# NB: candidates are built with the plain version "1.0.0" — the winner becomes
|
|
# the MAIN runner, and a tuning-suffixed version would leak machine-specific
|
|
# provenance into published results. The chosen flags are recorded separately
|
|
# in build/runners/c/equix_runner.flags and build/provenance.json.
|
|
NAMES=(); OKFLAGS=(); MEDIANS=()
|
|
for entry in "${CANDIDATES[@]}"; do
|
|
IFS='|' read -r name flags <<<"$entry"
|
|
bdir="build/autotune/$name"
|
|
log="build/autotune/$name.log"
|
|
mkdir -p build/autotune
|
|
# A copied/moved repo carries a CMake cache with old absolute paths; clean it.
|
|
if [ -f "$bdir/CMakeCache.txt" ]; then
|
|
recorded="$(sed -n 's/^CMAKE_CACHEFILE_DIR:INTERNAL=//p' "$bdir/CMakeCache.txt" | head -1)"
|
|
if [ -n "$recorded" ] && [ "$recorded" != "$(cd "$bdir" && pwd)" ]; then
|
|
rm -rf "$bdir"
|
|
fi
|
|
fi
|
|
if ! cmake -S runners/c -B "$bdir" \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DCMAKE_C_COMPILER="$CC" \
|
|
-DCMAKE_C_FLAGS_RELEASE="$flags" \
|
|
-DCMAKE_POLICY_VERSION_MINIMUM=3.10 \
|
|
-DEQUIX_C_COMMIT="$EQUIX_COMMIT" \
|
|
-DEQUIX_C_VERSION="1.0.0" >/dev/null 2>"$log" \
|
|
|| ! cmake --build "$bdir" -j"$NPROC" --target equix_runner >>"$log" 2>&1; then
|
|
echo " skip $name ($flags): build failed (see $log)"
|
|
continue
|
|
fi
|
|
if ! median="$(measure "$bdir/equix_runner")"; then
|
|
echo " skip $name ($flags): benchmark failed / no solutions"
|
|
continue
|
|
fi
|
|
NAMES+=("$name"); OKFLAGS+=("$flags"); MEDIANS+=("$median")
|
|
printf ' %-10s %-26s median %s ms\n' "$name" "$flags" \
|
|
"$(python3 -c "print(f'{$median/1e6:.3f}')")"
|
|
done
|
|
|
|
[ "${#NAMES[@]}" -gt 0 ] || { echo "autotune: no candidate usable; keeping existing runner"; exit 1; }
|
|
|
|
# Pick the minimum-median candidate; find plain -O3's index for the tie-break.
|
|
best_i=0; o3_i=-1
|
|
for i in "${!NAMES[@]}"; do
|
|
[ "${MEDIANS[$i]}" -lt "${MEDIANS[$best_i]}" ] && best_i=$i
|
|
[ "${NAMES[$i]}" = "o3" ] && o3_i=$i
|
|
done
|
|
|
|
# Tie-break: if plain -O3 is within EPS of the winner, prefer it -- avoids
|
|
# locking the main runner into a native/LTO build over a sub-noise difference.
|
|
if [ "$o3_i" -ge 0 ] && [ "${NAMES[$best_i]}" != "o3" ] \
|
|
&& awk "BEGIN{exit !(${MEDIANS[$o3_i]} <= ${MEDIANS[$best_i]}*(1+$EPS))}"; then
|
|
echo " note: -O3 within ${EPS} of best (${NAMES[$best_i]}); preferring -O3 for portability"
|
|
best_i=$o3_i
|
|
fi
|
|
|
|
best_name="${NAMES[$best_i]}"; best_flags="${OKFLAGS[$best_i]}"; best_median="${MEDIANS[$best_i]}"
|
|
mkdir -p build/runners/c
|
|
# Install atomically: copy to a temp path, then rename over the live runner, so a
|
|
# Ctrl+C during the copy can never leave a half-written (corrupt) main binary.
|
|
cp -f "build/autotune/$best_name/equix_runner" build/runners/c/equix_runner.tmp
|
|
mv -f build/runners/c/equix_runner.tmp build/runners/c/equix_runner
|
|
printf '%s' "$best_flags" > build/runners/c/equix_runner.flags
|
|
echo "==> fastest flags on this machine: '$best_flags' ($best_name), median $(python3 -c "print(f'{$best_median/1e6:.3f}')") ms"
|
|
echo " installed as build/runners/c/equix_runner (used by the 'equix-c' main run)"
|