ci: Add ix bench plots command to set bencher plots - #483

Merged
samuelburnham merged 8 commits into
mainfrom
ci-fixes
Jul 10, 2026
Merged

ci: Add ix bench plots command to set bencher plots#483
samuelburnham merged 8 commits into
mainfrom
ci-fixes

Conversation

@samuelburnham

Copy link
Copy Markdown
Member
  • ix bench plots command takes the latest metrics and parameters and uses the bencher CLI to recreate the dashboard plots accordingly
  • Parses env vars on a single line instead of only multiple for the !benchmark workflow
  • Fixes the compile Mathlib env as a !benchmark input
  • Prints Aiur prove throughput in the !benchmark comment
  • Removes phase printing from the !benchmark comment by default, add it back with BENCH_PHASES=1
  • Prunes Vectors.csv to remove redundant constants, adds Std.Hashmap as a heavy test
  • Fixes Zisk and SP1 peak-rss measurement by bumping tracing-texray in their workspaces, and now collects/prints shards and total constants correctly

Local aiur prove runs plus per-constant closure profiles (targets,
ingress bytes, fft-cost) showed a third of the cheap tier measuring
the same thing:
- drop the base-cost floor duplicates (3-9 constant closures, <13 kB,
prove within noise of each other): Acc.rec, Trans.mk, Sum.elim,
Prod.map, Option.bind, Array.toList — Eq.rec stays as the recursor
representative, Except.bind for monadic bind, WellFounded.fix for
recursion combinators;
- drop Nat.toDigits (strictly inside Nat.repr's closure), Array.filter
and Array.foldlM (within 1.5x of Array.map, which stays), Int.add
(scale twin of primary Nat.add_comm; Int is covered by emod/gcd);
- drop the Int8/Int16/Int32 instRxcHasSize_eq triplets (byte-identical
siblings of the kept Int64) and the trivials Nat, HEq, HEq.rec,
Nat.add;
- add Std.HashMap as HEAVY: 1,982-constant closure, 2.28 MB ingress,
84.6 s / 49 GiB / 18.4B FFTs proved locally — 6-40x every cheap
constant on every axis.
The prove-feasible full set is now 20 constants spanning recursors,
WF recursion, monadic bind, List/Array, Nat/Int/BitVec/USize
arithmetic, decidability, bytes/strings, hashing, and (via the heavy
tier) big structures.
The zisk and sp1 hosts pinned tracing-texray at an older rev than the
root workspace (bumped in #467), so cargo linked TWO instances of the
crate into each host: main() started one instance's RSS sampler while
ix_bench::peak_rss_bytes() read the other's never-started one — always
None — and the resulting null was dropped by `ix bench bmf`'s
numeric-fields filter. Net effect: every zisk upload silently lost
peak-rss (parent and per-shard rows alike) since the pin diverged.
Align both hosts on the root rev and re-lock; cargo tree confirms a
single shared instance, so the sampler the host starts is the one the
row emission reads.
constants = the named constants certified over the checked closure —
the pre-shard input set, the same universe the aiur rows count via
closureFrom. The cheap paths emit the cover size; the shard-plan path
emits `needed` (targets this run answers for), computed before shard
partitioning and store-reuse filtering, so neither sharding overhead
nor covered-shard skips distort it. Anon-work dedup shrinks the WORK
item count, never this number; sharding's extra work stays visible in
cycles/execute-time where it belongs.
shards was already emitted on shard-plan rows; a non-sharded execute
now reports 1 (a single leaf IS one shard) instead of omitting the
field and rendering n/a in the compare table.
bench-main pins constants 0/0 on the zkvm testbeds like the other
cells: it is deterministic, and a drop means lost coverage, not a win.
- aiur prove cells report throughput (constants/prove-time was already
uploaded and thresholded; the registry column list just omitted it —
every other cell follows the time/throughput/peak-rss convention).
- zisk compare tables gain constants and shards columns, next to cycles.
- The per-constant phase drill-downs are now opt-in via BENCH_PHASES=1
(a passthrough config key): the spans are noisy and dynamically
named, so the default comment stays at the headline measures.
- KEY=VALUE config parses inline on the command line too, whitespace-
separated — the single-line form for bench-pr.yml's manual
workflow_dispatch, whose input box cannot hold newlines. Inline keys
are strict (an unknown key rejects, like a typo'd backend); config
lines stay lenient since comment prose contains `=`.
One plot per (testbed, measure) bench-main tracks, one line per
benchmark row the cell uploads, plus the cross-kernel input-constants
overlay (aiur and zisk report the same pre-shard closure count, so the
paired lines must coincide — separation is a coverage-drift tripwire).
Titles, dashboard ordering, redundancy skips, and canonical measure
units live here as typed data; the spec derives from the registry +
Vectors.csv, so nothing is hand-listed.
Idempotent, keyed by title: matching plots are kept (index
re-asserted), stale ones deleted and recreated (the plot PATCH endpoint
only takes index/title/window), hand-pinned ones untouched. The sync
also asserts measure units — bencher auto-creates measures with
placeholder units on first upload, leaving plots unitless.
All bencher.dev traffic goes through the bencher CLI; --dry-run
previews with no key. bencher-plots.yml dispatches it manually (like
bencher-thresholds-reset) off the freshest bench-bins cache: run it
after a registry change has merged AND bench-main has built it — the
registry is compiled into the binary, and a new constant only gets its
plot line once its first rows upload.
…le subpackage
BENCH_ENVS=Mathlib died at 'Get Mathlib Cache' with 'unknown executable
cache': use-mathlib-cache runs `lake exe cache get`, and mathlib is a
dependency of the Benchmarks/Compile subpackage, not the root workspace
lean-action was pointed at. bench-main had this right; bench-pr now
mirrors it:
- compile job: provision from Benchmarks/Compile (same toolchain as
root) and `lake build Compile<env>` before the measured compile;
- benchmark cells: no mathlib cache at all — the .ixe comes from the
compile job, and the lazy Mathlib fallback fails loudly rather than
fetching oleans it still couldn't build;
- base side: the build action stays on the base root (it builds ix);
a separate step fetches/builds base mathlib oleans in the subpackage
when a Mathlib cell must re-run the base.
The benchmark cells carried two lazy fallbacks for artifacts the
compile job publishes in the same run: a missing compile row made the
compile cell re-measure the compile in-cell, and a missing `.ixe` made
`ix bench run` recompile the env fresh. Neither could ever work for
the Mathlib env (its oleans live only in the compile job's subpackage
provisioning), so the fallbacks meant InitStd and Mathlib failed
differently for the same infrastructure problem.
Both restores are now fail-on-cache-miss and the run step always
passes `--ixe`: the artifacts were published moments earlier, so a
miss is an infrastructure failure and every env fails the same way,
loudly, at the restore step. The in-cell "Save PR .ixe" step goes with
the fallback that produced it.
The zkVM hosts uploaded cycles/second under the same `throughput` slug
every other cell used for constants/second, so the shared measure had
no honest unit (and the dashboard showed a generic "per second"). The
hosts now report constants/second like everyone else — cheap zisk rows
over the certified cover, shard-plan rows over the pre-shard `needed`
set, sp1 over its checked count (its stdout already printed this
number; the row disagreed) — via one shared `ix_bench::throughput`
calculator. A zkVM's cycle rate stays derivable from its `cycles` and
`execute-time` fields.
The bencher measure's canonical units become "constants / second".
NB: the first post-merge zisk upload drops throughput by orders of
magnitude (meaning change, not a regression) — reset the
zisk-check-execute baseline (!bencher-thresholds-reset) right after,
or the 10% lower bound alerts.
@samuelburnham
samuelburnham merged commit e6daffc into mainJul 10, 2026
17 of 18 checks passed
@samuelburnham
samuelburnham deleted the ci-fixes branch July 10, 2026 21:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@samuelburnham@arthurpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

ci: Add ix bench plots command to set bencher plots - #483

Merged
samuelburnham merged 8 commits into
mainfrom
ci-fixes
Jul 10, 2026
Merged

ci: Add ix bench plots command to set bencher plots#483
samuelburnham merged 8 commits into
mainfrom
ci-fixes

Conversation

@samuelburnham

Copy link
Copy Markdown
Member
  • ix bench plots command takes the latest metrics and parameters and uses the bencher CLI to recreate the dashboard plots accordingly
  • Parses env vars on a single line instead of only multiple for the !benchmark workflow
  • Fixes the compile Mathlib env as a !benchmark input
  • Prints Aiur prove throughput in the !benchmark comment
  • Removes phase printing from the !benchmark comment by default, add it back with BENCH_PHASES=1
  • Prunes Vectors.csv to remove redundant constants, adds Std.Hashmap as a heavy test
  • Fixes Zisk and SP1 peak-rss measurement by bumping tracing-texray in their workspaces, and now collects/prints shards and total constants correctly

Local aiur prove runs plus per-constant closure profiles (targets,
ingress bytes, fft-cost) showed a third of the cheap tier measuring
the same thing:
- drop the base-cost floor duplicates (3-9 constant closures, <13 kB,
prove within noise of each other): Acc.rec, Trans.mk, Sum.elim,
Prod.map, Option.bind, Array.toList — Eq.rec stays as the recursor
representative, Except.bind for monadic bind, WellFounded.fix for
recursion combinators;
- drop Nat.toDigits (strictly inside Nat.repr's closure), Array.filter
and Array.foldlM (within 1.5x of Array.map, which stays), Int.add
(scale twin of primary Nat.add_comm; Int is covered by emod/gcd);
- drop the Int8/Int16/Int32 instRxcHasSize_eq triplets (byte-identical
siblings of the kept Int64) and the trivials Nat, HEq, HEq.rec,
Nat.add;
- add Std.HashMap as HEAVY: 1,982-constant closure, 2.28 MB ingress,
84.6 s / 49 GiB / 18.4B FFTs proved locally — 6-40x every cheap
constant on every axis.
The prove-feasible full set is now 20 constants spanning recursors,
WF recursion, monadic bind, List/Array, Nat/Int/BitVec/USize
arithmetic, decidability, bytes/strings, hashing, and (via the heavy
tier) big structures.
The zisk and sp1 hosts pinned tracing-texray at an older rev than the
root workspace (bumped in #467), so cargo linked TWO instances of the
crate into each host: main() started one instance's RSS sampler while
ix_bench::peak_rss_bytes() read the other's never-started one — always
None — and the resulting null was dropped by `ix bench bmf`'s
numeric-fields filter. Net effect: every zisk upload silently lost
peak-rss (parent and per-shard rows alike) since the pin diverged.
Align both hosts on the root rev and re-lock; cargo tree confirms a
single shared instance, so the sampler the host starts is the one the
row emission reads.
constants = the named constants certified over the checked closure —
the pre-shard input set, the same universe the aiur rows count via
closureFrom. The cheap paths emit the cover size; the shard-plan path
emits `needed` (targets this run answers for), computed before shard
partitioning and store-reuse filtering, so neither sharding overhead
nor covered-shard skips distort it. Anon-work dedup shrinks the WORK
item count, never this number; sharding's extra work stays visible in
cycles/execute-time where it belongs.
shards was already emitted on shard-plan rows; a non-sharded execute
now reports 1 (a single leaf IS one shard) instead of omitting the
field and rendering n/a in the compare table.
bench-main pins constants 0/0 on the zkvm testbeds like the other
cells: it is deterministic, and a drop means lost coverage, not a win.
- aiur prove cells report throughput (constants/prove-time was already
uploaded and thresholded; the registry column list just omitted it —
every other cell follows the time/throughput/peak-rss convention).
- zisk compare tables gain constants and shards columns, next to cycles.
- The per-constant phase drill-downs are now opt-in via BENCH_PHASES=1
(a passthrough config key): the spans are noisy and dynamically
named, so the default comment stays at the headline measures.
- KEY=VALUE config parses inline on the command line too, whitespace-
separated — the single-line form for bench-pr.yml's manual
workflow_dispatch, whose input box cannot hold newlines. Inline keys
are strict (an unknown key rejects, like a typo'd backend); config
lines stay lenient since comment prose contains `=`.
One plot per (testbed, measure) bench-main tracks, one line per
benchmark row the cell uploads, plus the cross-kernel input-constants
overlay (aiur and zisk report the same pre-shard closure count, so the
paired lines must coincide — separation is a coverage-drift tripwire).
Titles, dashboard ordering, redundancy skips, and canonical measure
units live here as typed data; the spec derives from the registry +
Vectors.csv, so nothing is hand-listed.
Idempotent, keyed by title: matching plots are kept (index
re-asserted), stale ones deleted and recreated (the plot PATCH endpoint
only takes index/title/window), hand-pinned ones untouched. The sync
also asserts measure units — bencher auto-creates measures with
placeholder units on first upload, leaving plots unitless.
All bencher.dev traffic goes through the bencher CLI; --dry-run
previews with no key. bencher-plots.yml dispatches it manually (like
bencher-thresholds-reset) off the freshest bench-bins cache: run it
after a registry change has merged AND bench-main has built it — the
registry is compiled into the binary, and a new constant only gets its
plot line once its first rows upload.
…le subpackage
BENCH_ENVS=Mathlib died at 'Get Mathlib Cache' with 'unknown executable
cache': use-mathlib-cache runs `lake exe cache get`, and mathlib is a
dependency of the Benchmarks/Compile subpackage, not the root workspace
lean-action was pointed at. bench-main had this right; bench-pr now
mirrors it:
- compile job: provision from Benchmarks/Compile (same toolchain as
root) and `lake build Compile<env>` before the measured compile;
- benchmark cells: no mathlib cache at all — the .ixe comes from the
compile job, and the lazy Mathlib fallback fails loudly rather than
fetching oleans it still couldn't build;
- base side: the build action stays on the base root (it builds ix);
a separate step fetches/builds base mathlib oleans in the subpackage
when a Mathlib cell must re-run the base.
The benchmark cells carried two lazy fallbacks for artifacts the
compile job publishes in the same run: a missing compile row made the
compile cell re-measure the compile in-cell, and a missing `.ixe` made
`ix bench run` recompile the env fresh. Neither could ever work for
the Mathlib env (its oleans live only in the compile job's subpackage
provisioning), so the fallbacks meant InitStd and Mathlib failed
differently for the same infrastructure problem.
Both restores are now fail-on-cache-miss and the run step always
passes `--ixe`: the artifacts were published moments earlier, so a
miss is an infrastructure failure and every env fails the same way,
loudly, at the restore step. The in-cell "Save PR .ixe" step goes with
the fallback that produced it.
The zkVM hosts uploaded cycles/second under the same `throughput` slug
every other cell used for constants/second, so the shared measure had
no honest unit (and the dashboard showed a generic "per second"). The
hosts now report constants/second like everyone else — cheap zisk rows
over the certified cover, shard-plan rows over the pre-shard `needed`
set, sp1 over its checked count (its stdout already printed this
number; the row disagreed) — via one shared `ix_bench::throughput`
calculator. A zkVM's cycle rate stays derivable from its `cycles` and
`execute-time` fields.
The bencher measure's canonical units become "constants / second".
NB: the first post-merge zisk upload drops throughput by orders of
magnitude (meaning change, not a regression) — reset the
zisk-check-execute baseline (!bencher-thresholds-reset) right after,
or the 10% lower bound alerts.
@samuelburnham
samuelburnham merged commit e6daffc into mainJul 10, 2026
17 of 18 checks passed
@samuelburnham
samuelburnham deleted the ci-fixes branch July 10, 2026 21:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@samuelburnham@arthurpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci: Add ix bench plots command to set bencher plots - #483

Merged
samuelburnham merged 8 commits into
mainfrom
ci-fixes
Jul 10, 2026
Merged

ci: Add ix bench plots command to set bencher plots#483
samuelburnham merged 8 commits into
mainfrom
ci-fixes

Conversation

@samuelburnham

Copy link
Copy Markdown
Member
  • ix bench plots command takes the latest metrics and parameters and uses the bencher CLI to recreate the dashboard plots accordingly
  • Parses env vars on a single line instead of only multiple for the !benchmark workflow
  • Fixes the compile Mathlib env as a !benchmark input
  • Prints Aiur prove throughput in the !benchmark comment
  • Removes phase printing from the !benchmark comment by default, add it back with BENCH_PHASES=1
  • Prunes Vectors.csv to remove redundant constants, adds Std.Hashmap as a heavy test
  • Fixes Zisk and SP1 peak-rss measurement by bumping tracing-texray in their workspaces, and now collects/prints shards and total constants correctly

Local aiur prove runs plus per-constant closure profiles (targets,
ingress bytes, fft-cost) showed a third of the cheap tier measuring
the same thing:
- drop the base-cost floor duplicates (3-9 constant closures, <13 kB,
prove within noise of each other): Acc.rec, Trans.mk, Sum.elim,
Prod.map, Option.bind, Array.toList — Eq.rec stays as the recursor
representative, Except.bind for monadic bind, WellFounded.fix for
recursion combinators;
- drop Nat.toDigits (strictly inside Nat.repr's closure), Array.filter
and Array.foldlM (within 1.5x of Array.map, which stays), Int.add
(scale twin of primary Nat.add_comm; Int is covered by emod/gcd);
- drop the Int8/Int16/Int32 instRxcHasSize_eq triplets (byte-identical
siblings of the kept Int64) and the trivials Nat, HEq, HEq.rec,
Nat.add;
- add Std.HashMap as HEAVY: 1,982-constant closure, 2.28 MB ingress,
84.6 s / 49 GiB / 18.4B FFTs proved locally — 6-40x every cheap
constant on every axis.
The prove-feasible full set is now 20 constants spanning recursors,
WF recursion, monadic bind, List/Array, Nat/Int/BitVec/USize
arithmetic, decidability, bytes/strings, hashing, and (via the heavy
tier) big structures.
The zisk and sp1 hosts pinned tracing-texray at an older rev than the
root workspace (bumped in #467), so cargo linked TWO instances of the
crate into each host: main() started one instance's RSS sampler while
ix_bench::peak_rss_bytes() read the other's never-started one — always
None — and the resulting null was dropped by `ix bench bmf`'s
numeric-fields filter. Net effect: every zisk upload silently lost
peak-rss (parent and per-shard rows alike) since the pin diverged.
Align both hosts on the root rev and re-lock; cargo tree confirms a
single shared instance, so the sampler the host starts is the one the
row emission reads.
constants = the named constants certified over the checked closure —
the pre-shard input set, the same universe the aiur rows count via
closureFrom. The cheap paths emit the cover size; the shard-plan path
emits `needed` (targets this run answers for), computed before shard
partitioning and store-reuse filtering, so neither sharding overhead
nor covered-shard skips distort it. Anon-work dedup shrinks the WORK
item count, never this number; sharding's extra work stays visible in
cycles/execute-time where it belongs.
shards was already emitted on shard-plan rows; a non-sharded execute
now reports 1 (a single leaf IS one shard) instead of omitting the
field and rendering n/a in the compare table.
bench-main pins constants 0/0 on the zkvm testbeds like the other
cells: it is deterministic, and a drop means lost coverage, not a win.
- aiur prove cells report throughput (constants/prove-time was already
uploaded and thresholded; the registry column list just omitted it —
every other cell follows the time/throughput/peak-rss convention).
- zisk compare tables gain constants and shards columns, next to cycles.
- The per-constant phase drill-downs are now opt-in via BENCH_PHASES=1
(a passthrough config key): the spans are noisy and dynamically
named, so the default comment stays at the headline measures.
- KEY=VALUE config parses inline on the command line too, whitespace-
separated — the single-line form for bench-pr.yml's manual
workflow_dispatch, whose input box cannot hold newlines. Inline keys
are strict (an unknown key rejects, like a typo'd backend); config
lines stay lenient since comment prose contains `=`.
One plot per (testbed, measure) bench-main tracks, one line per
benchmark row the cell uploads, plus the cross-kernel input-constants
overlay (aiur and zisk report the same pre-shard closure count, so the
paired lines must coincide — separation is a coverage-drift tripwire).
Titles, dashboard ordering, redundancy skips, and canonical measure
units live here as typed data; the spec derives from the registry +
Vectors.csv, so nothing is hand-listed.
Idempotent, keyed by title: matching plots are kept (index
re-asserted), stale ones deleted and recreated (the plot PATCH endpoint
only takes index/title/window), hand-pinned ones untouched. The sync
also asserts measure units — bencher auto-creates measures with
placeholder units on first upload, leaving plots unitless.
All bencher.dev traffic goes through the bencher CLI; --dry-run
previews with no key. bencher-plots.yml dispatches it manually (like
bencher-thresholds-reset) off the freshest bench-bins cache: run it
after a registry change has merged AND bench-main has built it — the
registry is compiled into the binary, and a new constant only gets its
plot line once its first rows upload.
…le subpackage
BENCH_ENVS=Mathlib died at 'Get Mathlib Cache' with 'unknown executable
cache': use-mathlib-cache runs `lake exe cache get`, and mathlib is a
dependency of the Benchmarks/Compile subpackage, not the root workspace
lean-action was pointed at. bench-main had this right; bench-pr now
mirrors it:
- compile job: provision from Benchmarks/Compile (same toolchain as
root) and `lake build Compile<env>` before the measured compile;
- benchmark cells: no mathlib cache at all — the .ixe comes from the
compile job, and the lazy Mathlib fallback fails loudly rather than
fetching oleans it still couldn't build;
- base side: the build action stays on the base root (it builds ix);
a separate step fetches/builds base mathlib oleans in the subpackage
when a Mathlib cell must re-run the base.
The benchmark cells carried two lazy fallbacks for artifacts the
compile job publishes in the same run: a missing compile row made the
compile cell re-measure the compile in-cell, and a missing `.ixe` made
`ix bench run` recompile the env fresh. Neither could ever work for
the Mathlib env (its oleans live only in the compile job's subpackage
provisioning), so the fallbacks meant InitStd and Mathlib failed
differently for the same infrastructure problem.
Both restores are now fail-on-cache-miss and the run step always
passes `--ixe`: the artifacts were published moments earlier, so a
miss is an infrastructure failure and every env fails the same way,
loudly, at the restore step. The in-cell "Save PR .ixe" step goes with
the fallback that produced it.
The zkVM hosts uploaded cycles/second under the same `throughput` slug
every other cell used for constants/second, so the shared measure had
no honest unit (and the dashboard showed a generic "per second"). The
hosts now report constants/second like everyone else — cheap zisk rows
over the certified cover, shard-plan rows over the pre-shard `needed`
set, sp1 over its checked count (its stdout already printed this
number; the row disagreed) — via one shared `ix_bench::throughput`
calculator. A zkVM's cycle rate stays derivable from its `cycles` and
`execute-time` fields.
The bencher measure's canonical units become "constants / second".
NB: the first post-merge zisk upload drops throughput by orders of
magnitude (meaning change, not a regression) — reset the
zisk-check-execute baseline (!bencher-thresholds-reset) right after,
or the 10% lower bound alerts.
@samuelburnham
samuelburnham merged commit e6daffc into mainJul 10, 2026
17 of 18 checks passed
@samuelburnham
samuelburnham deleted the ci-fixes branch July 10, 2026 21:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@samuelburnham@arthurpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci: Add ix bench plots command to set bencher plots - #483

Merged
samuelburnham merged 8 commits into
mainfrom
ci-fixes
Jul 10, 2026
Merged

ci: Add ix bench plots command to set bencher plots#483
samuelburnham merged 8 commits into
mainfrom
ci-fixes

Conversation

@samuelburnham

Copy link
Copy Markdown
Member
  • ix bench plots command takes the latest metrics and parameters and uses the bencher CLI to recreate the dashboard plots accordingly
  • Parses env vars on a single line instead of only multiple for the !benchmark workflow
  • Fixes the compile Mathlib env as a !benchmark input
  • Prints Aiur prove throughput in the !benchmark comment
  • Removes phase printing from the !benchmark comment by default, add it back with BENCH_PHASES=1
  • Prunes Vectors.csv to remove redundant constants, adds Std.Hashmap as a heavy test
  • Fixes Zisk and SP1 peak-rss measurement by bumping tracing-texray in their workspaces, and now collects/prints shards and total constants correctly

Local aiur prove runs plus per-constant closure profiles (targets,
ingress bytes, fft-cost) showed a third of the cheap tier measuring
the same thing:
- drop the base-cost floor duplicates (3-9 constant closures, <13 kB,
prove within noise of each other): Acc.rec, Trans.mk, Sum.elim,
Prod.map, Option.bind, Array.toList — Eq.rec stays as the recursor
representative, Except.bind for monadic bind, WellFounded.fix for
recursion combinators;
- drop Nat.toDigits (strictly inside Nat.repr's closure), Array.filter
and Array.foldlM (within 1.5x of Array.map, which stays), Int.add
(scale twin of primary Nat.add_comm; Int is covered by emod/gcd);
- drop the Int8/Int16/Int32 instRxcHasSize_eq triplets (byte-identical
siblings of the kept Int64) and the trivials Nat, HEq, HEq.rec,
Nat.add;
- add Std.HashMap as HEAVY: 1,982-constant closure, 2.28 MB ingress,
84.6 s / 49 GiB / 18.4B FFTs proved locally — 6-40x every cheap
constant on every axis.
The prove-feasible full set is now 20 constants spanning recursors,
WF recursion, monadic bind, List/Array, Nat/Int/BitVec/USize
arithmetic, decidability, bytes/strings, hashing, and (via the heavy
tier) big structures.
The zisk and sp1 hosts pinned tracing-texray at an older rev than the
root workspace (bumped in #467), so cargo linked TWO instances of the
crate into each host: main() started one instance's RSS sampler while
ix_bench::peak_rss_bytes() read the other's never-started one — always
None — and the resulting null was dropped by `ix bench bmf`'s
numeric-fields filter. Net effect: every zisk upload silently lost
peak-rss (parent and per-shard rows alike) since the pin diverged.
Align both hosts on the root rev and re-lock; cargo tree confirms a
single shared instance, so the sampler the host starts is the one the
row emission reads.
constants = the named constants certified over the checked closure —
the pre-shard input set, the same universe the aiur rows count via
closureFrom. The cheap paths emit the cover size; the shard-plan path
emits `needed` (targets this run answers for), computed before shard
partitioning and store-reuse filtering, so neither sharding overhead
nor covered-shard skips distort it. Anon-work dedup shrinks the WORK
item count, never this number; sharding's extra work stays visible in
cycles/execute-time where it belongs.
shards was already emitted on shard-plan rows; a non-sharded execute
now reports 1 (a single leaf IS one shard) instead of omitting the
field and rendering n/a in the compare table.
bench-main pins constants 0/0 on the zkvm testbeds like the other
cells: it is deterministic, and a drop means lost coverage, not a win.
- aiur prove cells report throughput (constants/prove-time was already
uploaded and thresholded; the registry column list just omitted it —
every other cell follows the time/throughput/peak-rss convention).
- zisk compare tables gain constants and shards columns, next to cycles.
- The per-constant phase drill-downs are now opt-in via BENCH_PHASES=1
(a passthrough config key): the spans are noisy and dynamically
named, so the default comment stays at the headline measures.
- KEY=VALUE config parses inline on the command line too, whitespace-
separated — the single-line form for bench-pr.yml's manual
workflow_dispatch, whose input box cannot hold newlines. Inline keys
are strict (an unknown key rejects, like a typo'd backend); config
lines stay lenient since comment prose contains `=`.
One plot per (testbed, measure) bench-main tracks, one line per
benchmark row the cell uploads, plus the cross-kernel input-constants
overlay (aiur and zisk report the same pre-shard closure count, so the
paired lines must coincide — separation is a coverage-drift tripwire).
Titles, dashboard ordering, redundancy skips, and canonical measure
units live here as typed data; the spec derives from the registry +
Vectors.csv, so nothing is hand-listed.
Idempotent, keyed by title: matching plots are kept (index
re-asserted), stale ones deleted and recreated (the plot PATCH endpoint
only takes index/title/window), hand-pinned ones untouched. The sync
also asserts measure units — bencher auto-creates measures with
placeholder units on first upload, leaving plots unitless.
All bencher.dev traffic goes through the bencher CLI; --dry-run
previews with no key. bencher-plots.yml dispatches it manually (like
bencher-thresholds-reset) off the freshest bench-bins cache: run it
after a registry change has merged AND bench-main has built it — the
registry is compiled into the binary, and a new constant only gets its
plot line once its first rows upload.
…le subpackage
BENCH_ENVS=Mathlib died at 'Get Mathlib Cache' with 'unknown executable
cache': use-mathlib-cache runs `lake exe cache get`, and mathlib is a
dependency of the Benchmarks/Compile subpackage, not the root workspace
lean-action was pointed at. bench-main had this right; bench-pr now
mirrors it:
- compile job: provision from Benchmarks/Compile (same toolchain as
root) and `lake build Compile<env>` before the measured compile;
- benchmark cells: no mathlib cache at all — the .ixe comes from the
compile job, and the lazy Mathlib fallback fails loudly rather than
fetching oleans it still couldn't build;
- base side: the build action stays on the base root (it builds ix);
a separate step fetches/builds base mathlib oleans in the subpackage
when a Mathlib cell must re-run the base.
The benchmark cells carried two lazy fallbacks for artifacts the
compile job publishes in the same run: a missing compile row made the
compile cell re-measure the compile in-cell, and a missing `.ixe` made
`ix bench run` recompile the env fresh. Neither could ever work for
the Mathlib env (its oleans live only in the compile job's subpackage
provisioning), so the fallbacks meant InitStd and Mathlib failed
differently for the same infrastructure problem.
Both restores are now fail-on-cache-miss and the run step always
passes `--ixe`: the artifacts were published moments earlier, so a
miss is an infrastructure failure and every env fails the same way,
loudly, at the restore step. The in-cell "Save PR .ixe" step goes with
the fallback that produced it.
The zkVM hosts uploaded cycles/second under the same `throughput` slug
every other cell used for constants/second, so the shared measure had
no honest unit (and the dashboard showed a generic "per second"). The
hosts now report constants/second like everyone else — cheap zisk rows
over the certified cover, shard-plan rows over the pre-shard `needed`
set, sp1 over its checked count (its stdout already printed this
number; the row disagreed) — via one shared `ix_bench::throughput`
calculator. A zkVM's cycle rate stays derivable from its `cycles` and
`execute-time` fields.
The bencher measure's canonical units become "constants / second".
NB: the first post-merge zisk upload drops throughput by orders of
magnitude (meaning change, not a regression) — reset the
zisk-check-execute baseline (!bencher-thresholds-reset) right after,
or the 10% lower bound alerts.
@samuelburnham
samuelburnham merged commit e6daffc into mainJul 10, 2026
17 of 18 checks passed
@samuelburnham
samuelburnham deleted the ci-fixes branch July 10, 2026 21:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@samuelburnham@arthurpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

ci: Add ix bench plots command to set bencher plots - #483

Merged
samuelburnham merged 8 commits into
mainfrom
ci-fixes
Jul 10, 2026
Merged

ci: Add ix bench plots command to set bencher plots#483
samuelburnham merged 8 commits into
mainfrom
ci-fixes

Conversation

@samuelburnham

Copy link
Copy Markdown
Member
  • ix bench plots command takes the latest metrics and parameters and uses the bencher CLI to recreate the dashboard plots accordingly
  • Parses env vars on a single line instead of only multiple for the !benchmark workflow
  • Fixes the compile Mathlib env as a !benchmark input
  • Prints Aiur prove throughput in the !benchmark comment
  • Removes phase printing from the !benchmark comment by default, add it back with BENCH_PHASES=1
  • Prunes Vectors.csv to remove redundant constants, adds Std.Hashmap as a heavy test
  • Fixes Zisk and SP1 peak-rss measurement by bumping tracing-texray in their workspaces, and now collects/prints shards and total constants correctly

Local aiur prove runs plus per-constant closure profiles (targets,
ingress bytes, fft-cost) showed a third of the cheap tier measuring
the same thing:
- drop the base-cost floor duplicates (3-9 constant closures, <13 kB,
prove within noise of each other): Acc.rec, Trans.mk, Sum.elim,
Prod.map, Option.bind, Array.toList — Eq.rec stays as the recursor
representative, Except.bind for monadic bind, WellFounded.fix for
recursion combinators;
- drop Nat.toDigits (strictly inside Nat.repr's closure), Array.filter
and Array.foldlM (within 1.5x of Array.map, which stays), Int.add
(scale twin of primary Nat.add_comm; Int is covered by emod/gcd);
- drop the Int8/Int16/Int32 instRxcHasSize_eq triplets (byte-identical
siblings of the kept Int64) and the trivials Nat, HEq, HEq.rec,
Nat.add;
- add Std.HashMap as HEAVY: 1,982-constant closure, 2.28 MB ingress,
84.6 s / 49 GiB / 18.4B FFTs proved locally — 6-40x every cheap
constant on every axis.
The prove-feasible full set is now 20 constants spanning recursors,
WF recursion, monadic bind, List/Array, Nat/Int/BitVec/USize
arithmetic, decidability, bytes/strings, hashing, and (via the heavy
tier) big structures.
The zisk and sp1 hosts pinned tracing-texray at an older rev than the
root workspace (bumped in #467), so cargo linked TWO instances of the
crate into each host: main() started one instance's RSS sampler while
ix_bench::peak_rss_bytes() read the other's never-started one — always
None — and the resulting null was dropped by `ix bench bmf`'s
numeric-fields filter. Net effect: every zisk upload silently lost
peak-rss (parent and per-shard rows alike) since the pin diverged.
Align both hosts on the root rev and re-lock; cargo tree confirms a
single shared instance, so the sampler the host starts is the one the
row emission reads.
constants = the named constants certified over the checked closure —
the pre-shard input set, the same universe the aiur rows count via
closureFrom. The cheap paths emit the cover size; the shard-plan path
emits `needed` (targets this run answers for), computed before shard
partitioning and store-reuse filtering, so neither sharding overhead
nor covered-shard skips distort it. Anon-work dedup shrinks the WORK
item count, never this number; sharding's extra work stays visible in
cycles/execute-time where it belongs.
shards was already emitted on shard-plan rows; a non-sharded execute
now reports 1 (a single leaf IS one shard) instead of omitting the
field and rendering n/a in the compare table.
bench-main pins constants 0/0 on the zkvm testbeds like the other
cells: it is deterministic, and a drop means lost coverage, not a win.
- aiur prove cells report throughput (constants/prove-time was already
uploaded and thresholded; the registry column list just omitted it —
every other cell follows the time/throughput/peak-rss convention).
- zisk compare tables gain constants and shards columns, next to cycles.
- The per-constant phase drill-downs are now opt-in via BENCH_PHASES=1
(a passthrough config key): the spans are noisy and dynamically
named, so the default comment stays at the headline measures.
- KEY=VALUE config parses inline on the command line too, whitespace-
separated — the single-line form for bench-pr.yml's manual
workflow_dispatch, whose input box cannot hold newlines. Inline keys
are strict (an unknown key rejects, like a typo'd backend); config
lines stay lenient since comment prose contains `=`.
One plot per (testbed, measure) bench-main tracks, one line per
benchmark row the cell uploads, plus the cross-kernel input-constants
overlay (aiur and zisk report the same pre-shard closure count, so the
paired lines must coincide — separation is a coverage-drift tripwire).
Titles, dashboard ordering, redundancy skips, and canonical measure
units live here as typed data; the spec derives from the registry +
Vectors.csv, so nothing is hand-listed.
Idempotent, keyed by title: matching plots are kept (index
re-asserted), stale ones deleted and recreated (the plot PATCH endpoint
only takes index/title/window), hand-pinned ones untouched. The sync
also asserts measure units — bencher auto-creates measures with
placeholder units on first upload, leaving plots unitless.
All bencher.dev traffic goes through the bencher CLI; --dry-run
previews with no key. bencher-plots.yml dispatches it manually (like
bencher-thresholds-reset) off the freshest bench-bins cache: run it
after a registry change has merged AND bench-main has built it — the
registry is compiled into the binary, and a new constant only gets its
plot line once its first rows upload.
…le subpackage
BENCH_ENVS=Mathlib died at 'Get Mathlib Cache' with 'unknown executable
cache': use-mathlib-cache runs `lake exe cache get`, and mathlib is a
dependency of the Benchmarks/Compile subpackage, not the root workspace
lean-action was pointed at. bench-main had this right; bench-pr now
mirrors it:
- compile job: provision from Benchmarks/Compile (same toolchain as
root) and `lake build Compile<env>` before the measured compile;
- benchmark cells: no mathlib cache at all — the .ixe comes from the
compile job, and the lazy Mathlib fallback fails loudly rather than
fetching oleans it still couldn't build;
- base side: the build action stays on the base root (it builds ix);
a separate step fetches/builds base mathlib oleans in the subpackage
when a Mathlib cell must re-run the base.
The benchmark cells carried two lazy fallbacks for artifacts the
compile job publishes in the same run: a missing compile row made the
compile cell re-measure the compile in-cell, and a missing `.ixe` made
`ix bench run` recompile the env fresh. Neither could ever work for
the Mathlib env (its oleans live only in the compile job's subpackage
provisioning), so the fallbacks meant InitStd and Mathlib failed
differently for the same infrastructure problem.
Both restores are now fail-on-cache-miss and the run step always
passes `--ixe`: the artifacts were published moments earlier, so a
miss is an infrastructure failure and every env fails the same way,
loudly, at the restore step. The in-cell "Save PR .ixe" step goes with
the fallback that produced it.
The zkVM hosts uploaded cycles/second under the same `throughput` slug
every other cell used for constants/second, so the shared measure had
no honest unit (and the dashboard showed a generic "per second"). The
hosts now report constants/second like everyone else — cheap zisk rows
over the certified cover, shard-plan rows over the pre-shard `needed`
set, sp1 over its checked count (its stdout already printed this
number; the row disagreed) — via one shared `ix_bench::throughput`
calculator. A zkVM's cycle rate stays derivable from its `cycles` and
`execute-time` fields.
The bencher measure's canonical units become "constants / second".
NB: the first post-merge zisk upload drops throughput by orders of
magnitude (meaning change, not a regression) — reset the
zisk-check-execute baseline (!bencher-thresholds-reset) right after,
or the 10% lower bound alerts.
@samuelburnham
samuelburnham merged commit e6daffc into mainJul 10, 2026
17 of 18 checks passed
@samuelburnham
samuelburnham deleted the ci-fixes branch July 10, 2026 21:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@samuelburnham@arthurpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci: Add ix bench plots command to set bencher plots - #483

Merged
samuelburnham merged 8 commits into
mainfrom
ci-fixes
Jul 10, 2026
Merged

ci: Add ix bench plots command to set bencher plots#483
samuelburnham merged 8 commits into
mainfrom
ci-fixes

Conversation

@samuelburnham

Copy link
Copy Markdown
Member
  • ix bench plots command takes the latest metrics and parameters and uses the bencher CLI to recreate the dashboard plots accordingly
  • Parses env vars on a single line instead of only multiple for the !benchmark workflow
  • Fixes the compile Mathlib env as a !benchmark input
  • Prints Aiur prove throughput in the !benchmark comment
  • Removes phase printing from the !benchmark comment by default, add it back with BENCH_PHASES=1
  • Prunes Vectors.csv to remove redundant constants, adds Std.Hashmap as a heavy test
  • Fixes Zisk and SP1 peak-rss measurement by bumping tracing-texray in their workspaces, and now collects/prints shards and total constants correctly

Local aiur prove runs plus per-constant closure profiles (targets,
ingress bytes, fft-cost) showed a third of the cheap tier measuring
the same thing:
- drop the base-cost floor duplicates (3-9 constant closures, <13 kB,
prove within noise of each other): Acc.rec, Trans.mk, Sum.elim,
Prod.map, Option.bind, Array.toList — Eq.rec stays as the recursor
representative, Except.bind for monadic bind, WellFounded.fix for
recursion combinators;
- drop Nat.toDigits (strictly inside Nat.repr's closure), Array.filter
and Array.foldlM (within 1.5x of Array.map, which stays), Int.add
(scale twin of primary Nat.add_comm; Int is covered by emod/gcd);
- drop the Int8/Int16/Int32 instRxcHasSize_eq triplets (byte-identical
siblings of the kept Int64) and the trivials Nat, HEq, HEq.rec,
Nat.add;
- add Std.HashMap as HEAVY: 1,982-constant closure, 2.28 MB ingress,
84.6 s / 49 GiB / 18.4B FFTs proved locally — 6-40x every cheap
constant on every axis.
The prove-feasible full set is now 20 constants spanning recursors,
WF recursion, monadic bind, List/Array, Nat/Int/BitVec/USize
arithmetic, decidability, bytes/strings, hashing, and (via the heavy
tier) big structures.
The zisk and sp1 hosts pinned tracing-texray at an older rev than the
root workspace (bumped in #467), so cargo linked TWO instances of the
crate into each host: main() started one instance's RSS sampler while
ix_bench::peak_rss_bytes() read the other's never-started one — always
None — and the resulting null was dropped by `ix bench bmf`'s
numeric-fields filter. Net effect: every zisk upload silently lost
peak-rss (parent and per-shard rows alike) since the pin diverged.
Align both hosts on the root rev and re-lock; cargo tree confirms a
single shared instance, so the sampler the host starts is the one the
row emission reads.
constants = the named constants certified over the checked closure —
the pre-shard input set, the same universe the aiur rows count via
closureFrom. The cheap paths emit the cover size; the shard-plan path
emits `needed` (targets this run answers for), computed before shard
partitioning and store-reuse filtering, so neither sharding overhead
nor covered-shard skips distort it. Anon-work dedup shrinks the WORK
item count, never this number; sharding's extra work stays visible in
cycles/execute-time where it belongs.
shards was already emitted on shard-plan rows; a non-sharded execute
now reports 1 (a single leaf IS one shard) instead of omitting the
field and rendering n/a in the compare table.
bench-main pins constants 0/0 on the zkvm testbeds like the other
cells: it is deterministic, and a drop means lost coverage, not a win.
- aiur prove cells report throughput (constants/prove-time was already
uploaded and thresholded; the registry column list just omitted it —
every other cell follows the time/throughput/peak-rss convention).
- zisk compare tables gain constants and shards columns, next to cycles.
- The per-constant phase drill-downs are now opt-in via BENCH_PHASES=1
(a passthrough config key): the spans are noisy and dynamically
named, so the default comment stays at the headline measures.
- KEY=VALUE config parses inline on the command line too, whitespace-
separated — the single-line form for bench-pr.yml's manual
workflow_dispatch, whose input box cannot hold newlines. Inline keys
are strict (an unknown key rejects, like a typo'd backend); config
lines stay lenient since comment prose contains `=`.
One plot per (testbed, measure) bench-main tracks, one line per
benchmark row the cell uploads, plus the cross-kernel input-constants
overlay (aiur and zisk report the same pre-shard closure count, so the
paired lines must coincide — separation is a coverage-drift tripwire).
Titles, dashboard ordering, redundancy skips, and canonical measure
units live here as typed data; the spec derives from the registry +
Vectors.csv, so nothing is hand-listed.
Idempotent, keyed by title: matching plots are kept (index
re-asserted), stale ones deleted and recreated (the plot PATCH endpoint
only takes index/title/window), hand-pinned ones untouched. The sync
also asserts measure units — bencher auto-creates measures with
placeholder units on first upload, leaving plots unitless.
All bencher.dev traffic goes through the bencher CLI; --dry-run
previews with no key. bencher-plots.yml dispatches it manually (like
bencher-thresholds-reset) off the freshest bench-bins cache: run it
after a registry change has merged AND bench-main has built it — the
registry is compiled into the binary, and a new constant only gets its
plot line once its first rows upload.
…le subpackage
BENCH_ENVS=Mathlib died at 'Get Mathlib Cache' with 'unknown executable
cache': use-mathlib-cache runs `lake exe cache get`, and mathlib is a
dependency of the Benchmarks/Compile subpackage, not the root workspace
lean-action was pointed at. bench-main had this right; bench-pr now
mirrors it:
- compile job: provision from Benchmarks/Compile (same toolchain as
root) and `lake build Compile<env>` before the measured compile;
- benchmark cells: no mathlib cache at all — the .ixe comes from the
compile job, and the lazy Mathlib fallback fails loudly rather than
fetching oleans it still couldn't build;
- base side: the build action stays on the base root (it builds ix);
a separate step fetches/builds base mathlib oleans in the subpackage
when a Mathlib cell must re-run the base.
The benchmark cells carried two lazy fallbacks for artifacts the
compile job publishes in the same run: a missing compile row made the
compile cell re-measure the compile in-cell, and a missing `.ixe` made
`ix bench run` recompile the env fresh. Neither could ever work for
the Mathlib env (its oleans live only in the compile job's subpackage
provisioning), so the fallbacks meant InitStd and Mathlib failed
differently for the same infrastructure problem.
Both restores are now fail-on-cache-miss and the run step always
passes `--ixe`: the artifacts were published moments earlier, so a
miss is an infrastructure failure and every env fails the same way,
loudly, at the restore step. The in-cell "Save PR .ixe" step goes with
the fallback that produced it.
The zkVM hosts uploaded cycles/second under the same `throughput` slug
every other cell used for constants/second, so the shared measure had
no honest unit (and the dashboard showed a generic "per second"). The
hosts now report constants/second like everyone else — cheap zisk rows
over the certified cover, shard-plan rows over the pre-shard `needed`
set, sp1 over its checked count (its stdout already printed this
number; the row disagreed) — via one shared `ix_bench::throughput`
calculator. A zkVM's cycle rate stays derivable from its `cycles` and
`execute-time` fields.
The bencher measure's canonical units become "constants / second".
NB: the first post-merge zisk upload drops throughput by orders of
magnitude (meaning change, not a regression) — reset the
zisk-check-execute baseline (!bencher-thresholds-reset) right after,
or the 10% lower bound alerts.
@samuelburnham
samuelburnham merged commit e6daffc into mainJul 10, 2026
17 of 18 checks passed
@samuelburnham
samuelburnham deleted the ci-fixes branch July 10, 2026 21:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@samuelburnham@arthurpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci: Add ix bench plots command to set bencher plots - #483

Merged
samuelburnham merged 8 commits into
mainfrom
ci-fixes
Jul 10, 2026
Merged

ci: Add ix bench plots command to set bencher plots#483
samuelburnham merged 8 commits into
mainfrom
ci-fixes

Conversation

@samuelburnham

Copy link
Copy Markdown
Member
  • ix bench plots command takes the latest metrics and parameters and uses the bencher CLI to recreate the dashboard plots accordingly
  • Parses env vars on a single line instead of only multiple for the !benchmark workflow
  • Fixes the compile Mathlib env as a !benchmark input
  • Prints Aiur prove throughput in the !benchmark comment
  • Removes phase printing from the !benchmark comment by default, add it back with BENCH_PHASES=1
  • Prunes Vectors.csv to remove redundant constants, adds Std.Hashmap as a heavy test
  • Fixes Zisk and SP1 peak-rss measurement by bumping tracing-texray in their workspaces, and now collects/prints shards and total constants correctly

Local aiur prove runs plus per-constant closure profiles (targets,
ingress bytes, fft-cost) showed a third of the cheap tier measuring
the same thing:
- drop the base-cost floor duplicates (3-9 constant closures, <13 kB,
prove within noise of each other): Acc.rec, Trans.mk, Sum.elim,
Prod.map, Option.bind, Array.toList — Eq.rec stays as the recursor
representative, Except.bind for monadic bind, WellFounded.fix for
recursion combinators;
- drop Nat.toDigits (strictly inside Nat.repr's closure), Array.filter
and Array.foldlM (within 1.5x of Array.map, which stays), Int.add
(scale twin of primary Nat.add_comm; Int is covered by emod/gcd);
- drop the Int8/Int16/Int32 instRxcHasSize_eq triplets (byte-identical
siblings of the kept Int64) and the trivials Nat, HEq, HEq.rec,
Nat.add;
- add Std.HashMap as HEAVY: 1,982-constant closure, 2.28 MB ingress,
84.6 s / 49 GiB / 18.4B FFTs proved locally — 6-40x every cheap
constant on every axis.
The prove-feasible full set is now 20 constants spanning recursors,
WF recursion, monadic bind, List/Array, Nat/Int/BitVec/USize
arithmetic, decidability, bytes/strings, hashing, and (via the heavy
tier) big structures.
The zisk and sp1 hosts pinned tracing-texray at an older rev than the
root workspace (bumped in #467), so cargo linked TWO instances of the
crate into each host: main() started one instance's RSS sampler while
ix_bench::peak_rss_bytes() read the other's never-started one — always
None — and the resulting null was dropped by `ix bench bmf`'s
numeric-fields filter. Net effect: every zisk upload silently lost
peak-rss (parent and per-shard rows alike) since the pin diverged.
Align both hosts on the root rev and re-lock; cargo tree confirms a
single shared instance, so the sampler the host starts is the one the
row emission reads.
constants = the named constants certified over the checked closure —
the pre-shard input set, the same universe the aiur rows count via
closureFrom. The cheap paths emit the cover size; the shard-plan path
emits `needed` (targets this run answers for), computed before shard
partitioning and store-reuse filtering, so neither sharding overhead
nor covered-shard skips distort it. Anon-work dedup shrinks the WORK
item count, never this number; sharding's extra work stays visible in
cycles/execute-time where it belongs.
shards was already emitted on shard-plan rows; a non-sharded execute
now reports 1 (a single leaf IS one shard) instead of omitting the
field and rendering n/a in the compare table.
bench-main pins constants 0/0 on the zkvm testbeds like the other
cells: it is deterministic, and a drop means lost coverage, not a win.
- aiur prove cells report throughput (constants/prove-time was already
uploaded and thresholded; the registry column list just omitted it —
every other cell follows the time/throughput/peak-rss convention).
- zisk compare tables gain constants and shards columns, next to cycles.
- The per-constant phase drill-downs are now opt-in via BENCH_PHASES=1
(a passthrough config key): the spans are noisy and dynamically
named, so the default comment stays at the headline measures.
- KEY=VALUE config parses inline on the command line too, whitespace-
separated — the single-line form for bench-pr.yml's manual
workflow_dispatch, whose input box cannot hold newlines. Inline keys
are strict (an unknown key rejects, like a typo'd backend); config
lines stay lenient since comment prose contains `=`.
One plot per (testbed, measure) bench-main tracks, one line per
benchmark row the cell uploads, plus the cross-kernel input-constants
overlay (aiur and zisk report the same pre-shard closure count, so the
paired lines must coincide — separation is a coverage-drift tripwire).
Titles, dashboard ordering, redundancy skips, and canonical measure
units live here as typed data; the spec derives from the registry +
Vectors.csv, so nothing is hand-listed.
Idempotent, keyed by title: matching plots are kept (index
re-asserted), stale ones deleted and recreated (the plot PATCH endpoint
only takes index/title/window), hand-pinned ones untouched. The sync
also asserts measure units — bencher auto-creates measures with
placeholder units on first upload, leaving plots unitless.
All bencher.dev traffic goes through the bencher CLI; --dry-run
previews with no key. bencher-plots.yml dispatches it manually (like
bencher-thresholds-reset) off the freshest bench-bins cache: run it
after a registry change has merged AND bench-main has built it — the
registry is compiled into the binary, and a new constant only gets its
plot line once its first rows upload.
…le subpackage
BENCH_ENVS=Mathlib died at 'Get Mathlib Cache' with 'unknown executable
cache': use-mathlib-cache runs `lake exe cache get`, and mathlib is a
dependency of the Benchmarks/Compile subpackage, not the root workspace
lean-action was pointed at. bench-main had this right; bench-pr now
mirrors it:
- compile job: provision from Benchmarks/Compile (same toolchain as
root) and `lake build Compile<env>` before the measured compile;
- benchmark cells: no mathlib cache at all — the .ixe comes from the
compile job, and the lazy Mathlib fallback fails loudly rather than
fetching oleans it still couldn't build;
- base side: the build action stays on the base root (it builds ix);
a separate step fetches/builds base mathlib oleans in the subpackage
when a Mathlib cell must re-run the base.
The benchmark cells carried two lazy fallbacks for artifacts the
compile job publishes in the same run: a missing compile row made the
compile cell re-measure the compile in-cell, and a missing `.ixe` made
`ix bench run` recompile the env fresh. Neither could ever work for
the Mathlib env (its oleans live only in the compile job's subpackage
provisioning), so the fallbacks meant InitStd and Mathlib failed
differently for the same infrastructure problem.
Both restores are now fail-on-cache-miss and the run step always
passes `--ixe`: the artifacts were published moments earlier, so a
miss is an infrastructure failure and every env fails the same way,
loudly, at the restore step. The in-cell "Save PR .ixe" step goes with
the fallback that produced it.
The zkVM hosts uploaded cycles/second under the same `throughput` slug
every other cell used for constants/second, so the shared measure had
no honest unit (and the dashboard showed a generic "per second"). The
hosts now report constants/second like everyone else — cheap zisk rows
over the certified cover, shard-plan rows over the pre-shard `needed`
set, sp1 over its checked count (its stdout already printed this
number; the row disagreed) — via one shared `ix_bench::throughput`
calculator. A zkVM's cycle rate stays derivable from its `cycles` and
`execute-time` fields.
The bencher measure's canonical units become "constants / second".
NB: the first post-merge zisk upload drops throughput by orders of
magnitude (meaning change, not a regression) — reset the
zisk-check-execute baseline (!bencher-thresholds-reset) right after,
or the 10% lower bound alerts.
@samuelburnham
samuelburnham merged commit e6daffc into mainJul 10, 2026
17 of 18 checks passed
@samuelburnham
samuelburnham deleted the ci-fixes branch July 10, 2026 21:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@samuelburnham@arthurpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

ci: Add ix bench plots command to set bencher plots - #483

Merged
samuelburnham merged 8 commits into
mainfrom
ci-fixes
Jul 10, 2026
Merged

ci: Add ix bench plots command to set bencher plots#483
samuelburnham merged 8 commits into
mainfrom
ci-fixes

Conversation

@samuelburnham

Copy link
Copy Markdown
Member
  • ix bench plots command takes the latest metrics and parameters and uses the bencher CLI to recreate the dashboard plots accordingly
  • Parses env vars on a single line instead of only multiple for the !benchmark workflow
  • Fixes the compile Mathlib env as a !benchmark input
  • Prints Aiur prove throughput in the !benchmark comment
  • Removes phase printing from the !benchmark comment by default, add it back with BENCH_PHASES=1
  • Prunes Vectors.csv to remove redundant constants, adds Std.Hashmap as a heavy test
  • Fixes Zisk and SP1 peak-rss measurement by bumping tracing-texray in their workspaces, and now collects/prints shards and total constants correctly

Local aiur prove runs plus per-constant closure profiles (targets,
ingress bytes, fft-cost) showed a third of the cheap tier measuring
the same thing:
- drop the base-cost floor duplicates (3-9 constant closures, <13 kB,
prove within noise of each other): Acc.rec, Trans.mk, Sum.elim,
Prod.map, Option.bind, Array.toList — Eq.rec stays as the recursor
representative, Except.bind for monadic bind, WellFounded.fix for
recursion combinators;
- drop Nat.toDigits (strictly inside Nat.repr's closure), Array.filter
and Array.foldlM (within 1.5x of Array.map, which stays), Int.add
(scale twin of primary Nat.add_comm; Int is covered by emod/gcd);
- drop the Int8/Int16/Int32 instRxcHasSize_eq triplets (byte-identical
siblings of the kept Int64) and the trivials Nat, HEq, HEq.rec,
Nat.add;
- add Std.HashMap as HEAVY: 1,982-constant closure, 2.28 MB ingress,
84.6 s / 49 GiB / 18.4B FFTs proved locally — 6-40x every cheap
constant on every axis.
The prove-feasible full set is now 20 constants spanning recursors,
WF recursion, monadic bind, List/Array, Nat/Int/BitVec/USize
arithmetic, decidability, bytes/strings, hashing, and (via the heavy
tier) big structures.
The zisk and sp1 hosts pinned tracing-texray at an older rev than the
root workspace (bumped in #467), so cargo linked TWO instances of the
crate into each host: main() started one instance's RSS sampler while
ix_bench::peak_rss_bytes() read the other's never-started one — always
None — and the resulting null was dropped by `ix bench bmf`'s
numeric-fields filter. Net effect: every zisk upload silently lost
peak-rss (parent and per-shard rows alike) since the pin diverged.
Align both hosts on the root rev and re-lock; cargo tree confirms a
single shared instance, so the sampler the host starts is the one the
row emission reads.
constants = the named constants certified over the checked closure —
the pre-shard input set, the same universe the aiur rows count via
closureFrom. The cheap paths emit the cover size; the shard-plan path
emits `needed` (targets this run answers for), computed before shard
partitioning and store-reuse filtering, so neither sharding overhead
nor covered-shard skips distort it. Anon-work dedup shrinks the WORK
item count, never this number; sharding's extra work stays visible in
cycles/execute-time where it belongs.
shards was already emitted on shard-plan rows; a non-sharded execute
now reports 1 (a single leaf IS one shard) instead of omitting the
field and rendering n/a in the compare table.
bench-main pins constants 0/0 on the zkvm testbeds like the other
cells: it is deterministic, and a drop means lost coverage, not a win.
- aiur prove cells report throughput (constants/prove-time was already
uploaded and thresholded; the registry column list just omitted it —
every other cell follows the time/throughput/peak-rss convention).
- zisk compare tables gain constants and shards columns, next to cycles.
- The per-constant phase drill-downs are now opt-in via BENCH_PHASES=1
(a passthrough config key): the spans are noisy and dynamically
named, so the default comment stays at the headline measures.
- KEY=VALUE config parses inline on the command line too, whitespace-
separated — the single-line form for bench-pr.yml's manual
workflow_dispatch, whose input box cannot hold newlines. Inline keys
are strict (an unknown key rejects, like a typo'd backend); config
lines stay lenient since comment prose contains `=`.
One plot per (testbed, measure) bench-main tracks, one line per
benchmark row the cell uploads, plus the cross-kernel input-constants
overlay (aiur and zisk report the same pre-shard closure count, so the
paired lines must coincide — separation is a coverage-drift tripwire).
Titles, dashboard ordering, redundancy skips, and canonical measure
units live here as typed data; the spec derives from the registry +
Vectors.csv, so nothing is hand-listed.
Idempotent, keyed by title: matching plots are kept (index
re-asserted), stale ones deleted and recreated (the plot PATCH endpoint
only takes index/title/window), hand-pinned ones untouched. The sync
also asserts measure units — bencher auto-creates measures with
placeholder units on first upload, leaving plots unitless.
All bencher.dev traffic goes through the bencher CLI; --dry-run
previews with no key. bencher-plots.yml dispatches it manually (like
bencher-thresholds-reset) off the freshest bench-bins cache: run it
after a registry change has merged AND bench-main has built it — the
registry is compiled into the binary, and a new constant only gets its
plot line once its first rows upload.
…le subpackage
BENCH_ENVS=Mathlib died at 'Get Mathlib Cache' with 'unknown executable
cache': use-mathlib-cache runs `lake exe cache get`, and mathlib is a
dependency of the Benchmarks/Compile subpackage, not the root workspace
lean-action was pointed at. bench-main had this right; bench-pr now
mirrors it:
- compile job: provision from Benchmarks/Compile (same toolchain as
root) and `lake build Compile<env>` before the measured compile;
- benchmark cells: no mathlib cache at all — the .ixe comes from the
compile job, and the lazy Mathlib fallback fails loudly rather than
fetching oleans it still couldn't build;
- base side: the build action stays on the base root (it builds ix);
a separate step fetches/builds base mathlib oleans in the subpackage
when a Mathlib cell must re-run the base.
The benchmark cells carried two lazy fallbacks for artifacts the
compile job publishes in the same run: a missing compile row made the
compile cell re-measure the compile in-cell, and a missing `.ixe` made
`ix bench run` recompile the env fresh. Neither could ever work for
the Mathlib env (its oleans live only in the compile job's subpackage
provisioning), so the fallbacks meant InitStd and Mathlib failed
differently for the same infrastructure problem.
Both restores are now fail-on-cache-miss and the run step always
passes `--ixe`: the artifacts were published moments earlier, so a
miss is an infrastructure failure and every env fails the same way,
loudly, at the restore step. The in-cell "Save PR .ixe" step goes with
the fallback that produced it.
The zkVM hosts uploaded cycles/second under the same `throughput` slug
every other cell used for constants/second, so the shared measure had
no honest unit (and the dashboard showed a generic "per second"). The
hosts now report constants/second like everyone else — cheap zisk rows
over the certified cover, shard-plan rows over the pre-shard `needed`
set, sp1 over its checked count (its stdout already printed this
number; the row disagreed) — via one shared `ix_bench::throughput`
calculator. A zkVM's cycle rate stays derivable from its `cycles` and
`execute-time` fields.
The bencher measure's canonical units become "constants / second".
NB: the first post-merge zisk upload drops throughput by orders of
magnitude (meaning change, not a regression) — reset the
zisk-check-execute baseline (!bencher-thresholds-reset) right after,
or the 10% lower bound alerts.
@samuelburnham
samuelburnham merged commit e6daffc into mainJul 10, 2026
17 of 18 checks passed
@samuelburnham
samuelburnham deleted the ci-fixes branch July 10, 2026 21:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@samuelburnham@arthurpaulino