Skip to content

Commit 770cf11

Browse files
committed
[CI] Build the benchmark baseline wheel against the base commit's LLVM
prepare-mlir builds one shared MLIR install from the PR's pin and uses it for both wheels. An LLVM pin bump also carries the source adaptation that the new pin requires, so the base commit cannot compile against it and the baseline wheel is never produced. The run ends at "No usable main benchmark baseline found" (run 32954898547, PR #1051), losing the vs-main comparison for exactly the PRs whose performance impact is least predictable. When the LLVM inputs differ from the base commit's, restore the MLIR install belonging to the base pin and build the baseline wheel against that. - ci_mlir_cache_key.sh derives the cache key from file contents, making it computable for the base commit. It replaces hashFiles() and serves both call sites, so the two keys cannot drift. - The baseline entry is restored before the shared one and under the same path. actions/cache derives its version from the path list, so a renamed restore would miss whatever the key said. - The unpacked install is checked against its VCSRevision.h. A wrong-pin baseline is worse than none: it yields a plausible number nobody queries. - prepare-mlir marks the wheel with the LLVM it was really built against, and the table is labelled from that marker. Deriving it from the pinned hashes would mislabel a bump that needed no source adaptation. A cache miss means no baseline. The wanted entry is the one every non-bump PR restores on every run, so a miss is the exception. Changing the key formula invalidates the cache, so every PR cold-builds LLVM until main's next push saves an entry under the new one.
1 parent bb1a1ff commit 770cf11

3 files changed

Lines changed: 214 additions & 5 deletions

File tree

.github/workflows/flydsl.yaml

Lines changed: 165 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,109 @@ jobs:
143143
ref: ${{ env.GITHUB_COMMIT_SHA }}
144144
path: flydsl-test
145145

146+
# A pin bump leaves the base source uncompilable against the PR's MLIR, so
147+
# the baseline wheel needs the base commit's own install. Every step gated
148+
# on pin_changed is skipped for a PR that leaves the LLVM inputs alone.
149+
- name: Resolve base LLVM pin
150+
id: base-pin
151+
# `run:` blocks are `bash -e`, and nothing decided here is worth failing
152+
# prepare-mlir over: with no outputs written, the gated steps skip.
153+
continue-on-error: true
154+
# Some self-hosted runners rewrite GitHub URLs to a git cache, and this
155+
# is the only fetch here that runs on the host, not in the container.
156+
env:
157+
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/flydsl-gitconfig
158+
GIT_CONFIG_NOSYSTEM: "1"
159+
run: |
160+
set -uo pipefail
161+
# Self-hosted workspaces are reused; drop what the last run left.
162+
rm -rf base-pin
163+
rm -f mlir_install_base.tgz
164+
mkdir -p base-pin/thirdparty base-pin/scripts
165+
if ! git -C flydsl-test fetch "https://github.com/${BASE_REPO_NAME}.git" \
166+
"${BASE_COMMIT_SHA}" --no-tags --depth=1; then
167+
echo "Could not fetch base ${BASE_REPO_NAME}@${BASE_COMMIT_SHA}; treating LLVM pin as unchanged."
168+
echo "pin_changed=false" >>"${GITHUB_OUTPUT}"
169+
exit 0
170+
fi
171+
for f in thirdparty/llvm-build-info.json thirdparty/llvm-rocdl-lld-argv0.patch scripts/build_llvm.sh; do
172+
if ! git -C flydsl-test show "FETCH_HEAD:${f}" >"base-pin/${f}"; then
173+
echo "Base commit has no ${f}; treating LLVM pin as unchanged."
174+
echo "pin_changed=false" >>"${GITHUB_OUTPUT}"
175+
exit 0
176+
fi
177+
done
178+
changed=false
179+
for f in thirdparty/llvm-build-info.json thirdparty/llvm-rocdl-lld-argv0.patch scripts/build_llvm.sh; do
180+
if ! diff -q "flydsl-test/${f}" "base-pin/${f}" >/dev/null; then
181+
echo "LLVM input differs from base: ${f}"
182+
changed=true
183+
fi
184+
done
185+
# sed, not python3: every other python call here runs in the container
186+
# and the runners promise no host interpreter. Scoped to "upstream" so a
187+
# second entry carrying its own llvm_hash cannot be picked up.
188+
base_hash="$(sed -n '/"upstream"/,/}/ s/.*"llvm_hash"[[:space:]]*:[[:space:]]*"\([0-9a-f]\{40\}\)".*/\1/p' \
189+
base-pin/thirdparty/llvm-build-info.json)"
190+
if [ -z "${base_hash}" ]; then
191+
echo "Could not read the base LLVM hash; treating LLVM pin as unchanged."
192+
echo "pin_changed=false" >>"${GITHUB_OUTPUT}"
193+
exit 0
194+
fi
195+
echo "pin_changed=${changed}" >>"${GITHUB_OUTPUT}"
196+
echo "base_llvm_hash=${base_hash}" >>"${GITHUB_OUTPUT}"
197+
echo "Base LLVM pin: ${base_hash}"
198+
199+
# One script for both keys so they cannot drift; content-derived, which is
200+
# what makes the base commit's key computable from the extracted files.
201+
- name: Compute MLIR cache keys
202+
id: keys
203+
run: |
204+
set -euo pipefail
205+
self_key="$(bash flydsl-test/scripts/ci_mlir_cache_key.sh flydsl-test)"
206+
echo "self=${self_key}" >>"${GITHUB_OUTPUT}"
207+
echo "Shared MLIR cache key: ${self_key}"
208+
if [ "${{ steps.base-pin.outputs.pin_changed }}" = "true" ]; then
209+
base_key="$(bash flydsl-test/scripts/ci_mlir_cache_key.sh base-pin)"
210+
echo "base=${base_key}" >>"${GITHUB_OUTPUT}"
211+
echo "Baseline MLIR cache key: ${base_key}"
212+
fi
213+
214+
# Restored before the shared entry and under the same `path`, then moved
215+
# aside: actions/cache derives its version from the path list, so an entry
216+
# saved as `mlir_install.tgz` is only findable under that name. No
217+
# restore-keys either - a prefix fallback would return an install built
218+
# from another pin.
219+
#
220+
# This is the entry every PR that leaves LLVM alone restores on every run,
221+
# so it stays warm. A PR that also changes MLIR_CACHE_VERSION or
222+
# LLVM_BUILD_PROFILE misses, since both feed the key from the PR side.
223+
- name: Restore baseline MLIR cache
224+
id: base-mlir-cache
225+
if: steps.base-pin.outputs.pin_changed == 'true'
226+
uses: actions/cache/restore@v4
227+
with:
228+
path: mlir_install.tgz
229+
key: ${{ steps.keys.outputs.base }}
230+
231+
- name: Stash baseline MLIR tarball
232+
if: steps.base-mlir-cache.outputs.cache-hit == 'true'
233+
continue-on-error: true
234+
run: |
235+
set -uo pipefail
236+
if [ ! -s mlir_install.tgz ]; then
237+
echo "::warning title=Benchmark baseline unavailable::Baseline MLIR cache reported a hit but produced no tarball."
238+
rm -f mlir_install.tgz
239+
exit 0
240+
fi
241+
mv mlir_install.tgz mlir_install_base.tgz
242+
146243
- name: Restore shared MLIR cache
147244
id: mlir-cache
148245
uses: actions/cache/restore@v4
149246
with:
150247
path: mlir_install.tgz
151-
key: mlir-install-${{ runner.os }}-${{ runner.arch }}-${{ env.MLIR_CACHE_VERSION }}-${{ env.LLVM_BUILD_PROFILE }}-${{ hashFiles('flydsl-test/thirdparty/llvm-build-info.json', 'flydsl-test/thirdparty/llvm-rocdl-lld-argv0.patch', 'flydsl-test/scripts/build_llvm.sh') }}
248+
key: ${{ steps.keys.outputs.self }}
152249

153250
- name: Start MLIR build container
154251
run: |
@@ -200,12 +297,56 @@ jobs:
200297
docker cp flydsl_mlir_cache:/llvm-project/mlir_install.tgz ./mlir_install.tgz
201298
test -s ./mlir_install.tgz
202299
300+
# Checked against the VCSRevision.h the install ships: a wrong-pin baseline
301+
# is worse than none, since it yields a plausible number nobody questions.
302+
#
303+
# Only a cache hit produces a baseline. Building the old pin here would
304+
# cost a second llvm-project clone and full LLVM build on a GPU runner, per
305+
# push, for an advisory number. Revisit if the logs show misses are common.
306+
- name: Unpack and verify baseline MLIR
307+
id: base-mlir
308+
if: steps.base-pin.outputs.pin_changed == 'true'
309+
continue-on-error: true
310+
run: |
311+
set -uo pipefail
312+
if [ ! -s ./mlir_install_base.tgz ]; then
313+
echo "::notice title=Benchmark baseline skipped::No cached MLIR install for the base commit's LLVM (${{ steps.base-pin.outputs.base_llvm_hash }}), so the vs-main comparison is skipped; vs latest tag is unaffected. This resolves itself once main has run under that pin."
314+
exit 0
315+
fi
316+
docker cp ./mlir_install_base.tgz flydsl_mlir_cache:/tmp/mlir_install_base.tgz
317+
docker exec flydsl_mlir_cache bash -c "
318+
set -e
319+
rm -rf /llvm-project/mlir_install_base
320+
mkdir -p /tmp/mlir_base_extract
321+
tar -xzf /tmp/mlir_install_base.tgz -C /tmp/mlir_base_extract
322+
mv /tmp/mlir_base_extract/mlir_install /llvm-project/mlir_install_base
323+
rmdir /tmp/mlir_base_extract
324+
test -d /llvm-project/mlir_install_base/lib/cmake/mlir
325+
" || { echo "::warning title=Benchmark baseline unavailable::Baseline MLIR install could not be unpacked."; exit 0; }
326+
want="${{ steps.base-pin.outputs.base_llvm_hash }}"
327+
got="$(docker exec flydsl_mlir_cache sed -n 's/.*LLVM_REVISION R"(\([0-9a-f]\{40\}\))".*/\1/p' \
328+
/llvm-project/mlir_install_base/include/llvm/Support/VCSRevision.h 2>/dev/null)"
329+
if [ -z "${got}" ]; then
330+
echo "Baseline MLIR carries no VC revision; relying on the content-derived cache key alone."
331+
elif [ "${got}" != "${want}" ]; then
332+
echo "::warning title=Benchmark baseline unavailable::Baseline MLIR is LLVM ${got}, expected ${want}; refusing to use it."
333+
docker exec flydsl_mlir_cache rm -rf /llvm-project/mlir_install_base
334+
exit 0
335+
else
336+
echo "Baseline MLIR verified at LLVM ${got}"
337+
fi
338+
echo "ready=true" >>"${GITHUB_OUTPUT}"
339+
203340
- name: Build current and base FlyDSL wheels
341+
env:
342+
BASE_MLIR_PATH: ${{ steps.base-mlir.outputs.ready == 'true' && '/llvm-project/mlir_install_base' || '' }}
343+
BASELINE_LLVM_HASH: ${{ steps.base-pin.outputs.base_llvm_hash }}
204344
run: |
205345
docker exec \
206346
-e BASE_REPO_NAME \
207347
-e BASE_COMMIT_SHA \
208348
-e ROCM_PATH \
349+
-e BASE_MLIR_PATH \
209350
flydsl_mlir_cache bash -c '
210351
export MLIR_PATH=/llvm-project/mlir_install
211352
export PATH="$(rocm-sdk path --bin):$PATH"
@@ -217,11 +358,25 @@ jobs:
217358
(cd flydsl-ci-wheels/pr && sha256sum -c SHA256SUMS)
218359
if [ -d flydsl-ci-wheels/base ]; then
219360
(cd flydsl-ci-wheels/base && sha256sum -c SHA256SUMS)
361+
# Mark which LLVM the wheel was really built against. A bump needing
362+
# no source adaptation still builds from the shared MLIR; labelling
363+
# that "old LLVM" points at a codegen difference that is not there.
364+
if [ -n "${BASE_MLIR_PATH}" ] && [ -n "${BASELINE_LLVM_HASH}" ]; then
365+
printf '%s\n' "${BASELINE_LLVM_HASH}" >flydsl-ci-wheels/base/BASELINE_LLVM
366+
fi
220367
else
221368
echo "::warning title=Baseline wheel unavailable::Uploading PR wheel without an exact base benchmark."
222369
fi
223370
(cd flydsl-ci-wheels/tools && sha256sum -c SHA256SUMS)
224371
372+
# The second install is the whole disk cost, and nothing below needs it.
373+
- name: Drop baseline MLIR install
374+
if: always() && steps.base-pin.outputs.pin_changed == 'true'
375+
continue-on-error: true
376+
run: |
377+
rm -f ./mlir_install_base.tgz
378+
docker exec flydsl_mlir_cache bash -c "rm -rf /llvm-project/mlir_install_base /tmp/mlir_install_base.tgz" || true
379+
225380
- name: Save shared MLIR cache
226381
if: >-
227382
steps.mlir-cache.outputs.cache-hit != 'true' &&
@@ -436,6 +591,14 @@ jobs:
436591
elif git fetch "${base_repo_url}" "${BASE_COMMIT_SHA}" --no-tags --depth=1; then
437592
commit="$(git rev-parse FETCH_HEAD)"
438593
label="main"
594+
# From prepare-mlir's marker, not from the pinned hashes: this job
595+
# cannot see which MLIR the baseline wheel was compiled with.
596+
display_label="main@${commit:0:8}"
597+
baseline_llvm="$(cat /flydsl-ci-wheels/base/BASELINE_LLVM 2>/dev/null || true)"
598+
if [ -n "${baseline_llvm}" ]; then
599+
display_label="${display_label} (llvm ${baseline_llvm:0:8})"
600+
echo "::notice title=Benchmark baseline uses a different LLVM::Baseline was built against LLVM ${baseline_llvm:0:8}, the PR against its own pin; deltas include LLVM codegen changes."
601+
fi
439602
worktree="/tmp/flydsl-bench-main"
440603
csv="/tmp/bench_main_candidate.csv"
441604
output="/tmp/bench_main.out"
@@ -460,7 +623,7 @@ jobs:
460623
status=$?
461624
if [ "${status}" -eq 0 ] && [ -s "${csv}" ]; then
462625
cp "${csv}" /tmp/bench_main.csv
463-
echo "${label}" >/tmp/bench_main_label
626+
echo "${display_label}" >/tmp/bench_main_label
464627
else
465628
echo "Exact main benchmark baseline failed; not retrying older commits."
466629
fi

scripts/build_ci_wheels.sh

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,23 @@ MLIR
7777
build_wheel() {
7878
local source_dir="$1"
7979
local label="$2"
80+
# The baseline wheel needs the base commit's own MLIR: on a pin bump the base
81+
# source predates the API change the new pin requires. The default keeps every
82+
# other wheel on the shared install.
83+
local mlir_path="${3:-${MLIR_PATH}}"
8084
local destination="${OUTPUT_DIR}/${label}"
8185
local wheels=()
8286
local fly_opt="${source_dir}/build-fly/build_py${PYTHON_SUFFIX}/bin/fly-opt"
8387

84-
echo "Building ${label} wheel from ${source_dir}"
88+
echo "Building ${label} wheel from ${source_dir} (MLIR: ${mlir_path})"
8589
if ! (
8690
cd "${source_dir}"
8791
env \
8892
PYTHON_VERSIONS="${PYTHON_VERSION}" \
8993
"${PYTHON_BIN_ENV}=${PYTHON_BIN}" \
9094
VENV_ROOT="${VENV_ROOT}" \
9195
ALLOW_ANY_GLIBC=1 \
92-
MLIR_PATH="${MLIR_PATH}" \
96+
MLIR_PATH="${mlir_path}" \
9397
bash scripts/build_wheels.sh
9498
); then
9599
return 1
@@ -146,7 +150,7 @@ build_base_wheel() {
146150
fi
147151

148152
trap cleanup_base_worktree EXIT
149-
if ! build_wheel "${BASE_WORKTREE}" base; then
153+
if ! build_wheel "${BASE_WORKTREE}" base "${BASE_MLIR_PATH:-${MLIR_PATH}}"; then
150154
cleanup_base_worktree
151155
trap - EXIT
152156
return 1

scripts/ci_mlir_cache_key.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: Apache-2.0
3+
# Copyright (c) 2025 FlyDSL Project Contributors
4+
#
5+
# Print the actions/cache key for the MLIR install that the given tree builds.
6+
#
7+
# Derived from file *contents*, not paths, so it is computable for any commit
8+
# whose LLVM inputs have been extracted anywhere on disk - that is what lets a PR
9+
# look up the install belonging to its base commit's pin. Both workflow call
10+
# sites use this script; inlining the formula elsewhere would let the two keys
11+
# drift and return an install built from the wrong pin.
12+
#
13+
# Total by construction: a missing input contributes a marker, not an error. It
14+
# replaced a hashFiles() expression, which tolerated missing files too, and a PR
15+
# that deletes one must still get a usable key rather than a failed job.
16+
set -euo pipefail
17+
18+
TREE_ROOT="${1:?usage: ci_mlir_cache_key.sh <tree-root>}"
19+
20+
INPUTS=(
21+
thirdparty/llvm-build-info.json
22+
thirdparty/llvm-rocdl-lld-argv0.patch
23+
scripts/build_llvm.sh
24+
)
25+
26+
# Per-file digests, so moving bytes across a file boundary changes the key.
27+
digests=""
28+
for input in "${INPUTS[@]}"; do
29+
if [[ -f "${TREE_ROOT}/${input}" ]]; then
30+
digests+="${input}:$(sha256sum <"${TREE_ROOT}/${input}" | cut -d' ' -f1)"$'\n'
31+
else
32+
digests+="${input}:absent"$'\n'
33+
fi
34+
done
35+
digest="$(printf '%s' "${digests}" | sha256sum | cut -c1-40)"
36+
37+
printf 'mlir-install-%s-%s-%s-%s-%s\n' \
38+
"${RUNNER_OS:?RUNNER_OS must be set}" \
39+
"${RUNNER_ARCH:?RUNNER_ARCH must be set}" \
40+
"${MLIR_CACHE_VERSION:?MLIR_CACHE_VERSION must be set}" \
41+
"${LLVM_BUILD_PROFILE:?LLVM_BUILD_PROFILE must be set}" \
42+
"${digest}"

0 commit comments

Comments
 (0)