Repository navigation
perf: §12.1's byte-identical levers, built behind flags and bound for measurement - #94
Merged
Merged
Conversation
Seven Tunables flags, off by default so the shipped path stays the reference each is timed against: a per-job dequantization table keyed on (bits, index) with ln(1+mu) hoisted, a four-lane forward DCT over coefficients, a cached decode gamma table, flat decode cosine tables, an early exit in the refinement scorer, a fused colour pass, and a bytewise bit writer. tests/accel_levers.rs holds every one to the spec vectors and to a differential sweep off the default tunables.
…for every lever bench_stages now marks dc_search, ac_quantize, refine and pack, so the residual it prints names no work (unmarked) instead of bounding four stages at once. The levers get their tune keys, sweep-label keys, and library tests the mutation sweep can run without spec/.
Each encode lever joins TUNE_ARMS at 100/256/512, the two that scale with the tier get a tier-4 block, the early exit is priced against refine_passes=1, and the decode levers get a block at tier 1 natural and tier 4 natural and capped, each against a shipped cell in the same block.
…loop The bit writer masks and shifts a field into one 64-bit word and ORs each byte it touches, which is the form §12.1 item 7 names; the flag is accel_word_bitpack to match. It and the fused colour pass now loop with a bounded for, so a mutated bound fails a test instead of hanging one.
…ound Names what each accel_* flag builds, what checks that it did not move a byte, and binds three tables of speedups to the perf arms that will time them. The cells stay placeholders until a quiet host commits a bounded run; the gate reports each as missing until then.
…ent mutant behind Goldens from master's encoder under band gains, CfL, refinement with a window, and a shared chroma scale kill the gain and window mutants the lever-vs-shipped comparisons cannot see. The hoisted quantize spells out its arms and drops a guard that decided nothing, and the two remaining equivalent mutants are excluded by line.
…s they were The fused-pass refactor had moved the shipped path's buffer allocations and shortened the linear-RGB planes' lifetime, which let later buffers reuse freed memory and made a 512x512 encode ~12% faster with every lever off; and the flat-table loop, sharing a function with the shipped render loop, made a tier-4 decode ~12% slower. Either would have moved the reference each lever is timed against. The shipped path now allocates, frees and renders as master does, measured stage by stage against master's own build; the flat loop is its own never-inlined function.
A share small enough to need an exponent (the refine stage, off in the shipped build) was written as 1.8e-05, which format:check:compare rejects in favour of 1.8e-5. The recorder now writes biome's spelling, anchored to whole numeric values so no string is touched.
…from it §1's table gains dc_search, ac_quantize, refine, pack and unmarked in place of the quantize_and_pack residual, and every prose figure that restated a §1 share is updated and bound, including the new ones §12.1 now quotes. The count of checked values is corrected to what the gate reports.
…d keep the agreeing pair's second The three columns were recorded back to back and kept only because the two agreed within 0.4 points per share and 2.2% per total; a recording taken during a load burst had not. Every §1-derived figure is set from the committed one.
…ved exclusion The CfL golden now splits the bands low enough that the luma gain lands on indices the predictor reads, killing the last gain mutant. The cfl_bits write guard's exclusion follows its line, and quantize_one's symmetric neighbourhood gets the same equivalence entry select_dc_codes has.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements
spec/PERFORMANCE.md§12.1's byte-identical acceleration levers behind flags, proves each one reproduces the shipped bytes, gives the levers inside the oldquantize_and_packresidual their ownstage!marks, adds a perf arm for every lever, and binds a new §12.4 of measured speedups throughverify:benchmark. The speedup cells stayTBD: they need a quiet-host bounded run, which is split off as #95.The levers (
rust/src/), each aTunables::accel_*flag, off inTunables::DEFAULT:accel_quant_tableencode.rs(AcQuantJob::accelerated/width/deq/quant, a table per bit width keyed on(bits, index)),mulaw.rs(mu_compress_by,compand_quantize_hoisted,dequantize_table)accel_dct_lanesdct.rsdct_encode_lanes: four coefficients per pass, each lane summing in the scalar order with the scalar term; portable Rust, no intrinsicsaccel_gamma_lut_cachedecode.rscached_gamma_lut(OnceLock, one table per transfer curve), used by the render and byaverage_coloraccel_flat_cosdct.rsprecompute_cos_table_flat/dct_decode_pixel_flat,decode.rsrender_flataccel_sse_early_exitencode.rssse_with_delta(…, stop_at)accel_fused_pixelsencode.rsanalyze: one tiled pass for linearize + OKLAB + the alpha reduction; the alpha plane is built only for an image with alphaaccel_word_bitpackbitpack.rswrite_bits_wordrust/src/constants.rs: the seven fields and theirDEFAULTs.master's own build on the same host, two lever refactors had moved it: the fused-pass rewrite shortened the linear-RGB planes' lifetime, so later buffers reused freed memory and a 512×512 encode got ~12% faster with every lever off; and the flat-table loop, sharing a function with the shipped render loop, made a tier-4 decode ~12% slower. Both are fixed (analyzeallocates and frees in master's order;render_flatis its own#[inline(never)]function), and the shipped path now matches master within noise at 512×512 stage by stage, at 100×100 tier 1 and tier 4, and on tier-1 and tier-4 decode.encode.rs):dc_search,ac_quantize,refine,packnow split whatquantize_and_packlumped together.rust/examples/bench_stages.rsprints the remaining residual asunmarked, andbench_decode_stages.rs's comment follows.rust/tests/accel_levers.rs(every encode, decode and capped vector with each lever alone and all on, plus a differential sweep under the shipped and eight non-defaultTunables), with a[[test]]entry inrust/Cargo.tomlgating it onspec-vectorslikespec_vectors; library tests inencode.rs,decode.rs,dct.rs,mulaw.rs,bitpack.rsso the mutation sweep holds every lever; and goldens produced by master's encoder for the lines the levers re-plumbed (band gains, CfL, refinement with a window, shared chroma scale).rust/.cargo/mutants.toml: three equivalent mutants excluded, each anchored to its line (the lanes DCT's scale floor; thecfl_bits > 0guard around two zero-width writes; andreplace + with - in quantize_oneatencode.rs:720:30, theac_nearestsearch over the symmetric offsets {0, ±1, ±2}, where negating the offset scans the same five candidates in another order and the strict<makes order matter only on an exact tie — the reasoning of the existingselect_dc_codesentry). That third mutant is not in new code: master has the same line atencode.rs:526with no exclusion; it enters the incremental sweep here because this PR re-plumbsquantize_one(job.quant/job.deq/job.width) and so puts the line in the diff. The existingmu_compressexclusion's note now says it also coversmu_compress_by.rust/examples/encode_stdin.rsandtools/comparison/src/verify-sweep-labels.ts: the seven tune keys, asconstants.rsrequires.tools/comparison/src/perf/matrix.ts,run.ts: the encode levers joinTUNE_ARMS(the early exit priced againstrefine_passes=1),TIER4_ACCEL_ARMSprices the tier-dependent ones at tier 4, andDECODE_ACCEL_ARMSprices the decode levers at tier 1 natural and tier 4 natural and capped, each against ashippedcell in the same block.tools/comparison/src/verify-benchmark.ts: §1's new rows; §12.4's three tables, bound withR.usso an unmeasured cell fails as missing rather than passing as unbound; prose bindings for every §1 figure the text now quotes.tools/comparison/baselines/perf-stages.json: §1 re-recorded at4d68c1fwith the new marks. Recorded twice back to back and kept only because the pair agreed (every share within 0.4 points, every total within 2.2%); an earlier recording taken during a load burst did not agree and was discarded.tools/benchmark/record_stages.py(+ test): writes float exponents the waybiome formatdoes (1.8e-6, not1.8e-06) — the newrefineshare needed one andformat:check:comparerejected the recorder's own output; the encode residual is namedunmarkedin its comment and fixture.spec/PERFORMANCE.md: §1's table, provenance and prose re-derived from the new recording; the figures quoted from it in §5, §6, §10 and §12 updated; §12 and §12.1's prose rewritten now thatac_quantizeis measured rather than bounded; §12.3 updated; new §12.4 (what each flag builds, what checks it did not move a byte, and three bound tables of speedups); §0's banner names §12.4's 34 cells among the gate's failures and corrects the checked-value count to 216 (it read 180; the gate reported 204 on master and 216 here).TESTING.md,.github/workflows/ci-comparison.yml: the lever tests listed, and the gate's failure count updated the same way (TESTING.md's heading now says 40 cells, six plus §12.4's 34, as its paragraph does).spec/PERFORMANCE.md§12.1 and §12.2: everyfile.rs:linecitation re-anchored to this head, since the levers moved the code they name (e.g.average_color's gamma-LUT builddecode.rs:546→660,mu_compress'sln(1 + mu)mulaw.rs:7→6,dct_decode_pixel_separable's cosine readdct.rs:269→370, the separable prototypedct.rs:296→424). Each citation names the shipped (flag-off) site.Informal speedups, not published. On this host (load average ~2.5 of 16 threads), Rust only, min of 7 reps × 2 rounds,
bench-encodewithCHROMAHASH_TUNE: at 100×100 tier 1,accel_quant_table1.48×,accel_dct_lanes1.91×, all levers 6.28×; at 512×512,accel_dct_lanes7.28×, all 7.51×; at 100×100 tier 4,accel_quant_table1.57×,accel_dct_lanes2.11×, all 9.06×;accel_fused_pixels1.04× andaccel_word_bitpack≤1.02× everywhere; the early exit 1.15× onrefine_passes=1. These are here to size review attention only. They are not in the document, because §0's rule is that a wall-clock figure is published from a committed run on a host that holds the 10% bar; #95 is that run.Validation
Re-run at
d618396(the head after the review repairs, which touch onlyspec/PERFORMANCE.mdandTESTING.md):cargo fmt --check,cargo clippy --all-targets --features full -- -D warnings,cargo test --features full,cargo build/test --no-default-features, the comparison tooling'sformat:check/lint/build,metric-selftest,verify-claims,verify-experiments --strict,verify-sweep-labels,corpus-licenses --check,spec/validate.py,check-versions.sh, and thetools/benchmarkruff and unittest rows all pass;verify-benchmarkfails with the same 40 placeholder disagreements and 216 checked values as below; the mutation sweep is as below.rd-gate,determinism-check, the lever on/off hashing and the timing comparison were not re-run for the repairs, which change no code. The rest of this section is the original validation, ata4c5df7.cargo fmt --check(rust/) — passcargo clippy --all-targets --features full -- -D warnings— pass (also withbench-internals)cargo test --features full— pass, every test binary (159 library tests, the 4 inaccel_levers, and the existing suites)cargo build --no-default-features/cargo test --no-default-features— passcargo check --target wasm32-unknown-unknown(rust/) — pass;aarch64-unknown-linux-gnu— unavailable (target not installed here)cargo mutants -d rust --in-diff <git -C rust diff --relative origin/master> -j 4(theci-mutants.ymlincremental job,--no-shuffle), re-run atd618396with all three exclusions in force — 417 mutants tested in 40m: 412 caught, 5 unviable, 0 missed, 0 timeouts. The excludedreplace + with - in quantize_oneis absent from the tested set; its sibling at the same site,replace + with * in quantize_one(encode.rs:720:30), is caught.python3 spec/validate.py— pass;tools/ci/check-versions.sh— passpython3 -m unittest discover -s tools/benchmark -p 'test_*.py',uvx ruff format --check tools/benchmark,uvx ruff check tools/benchmark— passmise run build:compare,lint:compare,format:check:compare— passnode dist/metric-selftest.js,verify-claims.js,verify-experiments.js --strict,verify-sweep-labels.js,corpus-licenses.js --check— passnode dist/verify-benchmark.js— fails, as expected and as on master: 40 disagreements, all placeholders — master's six (§2 tier 3/4 encode, §3's 128/1024 rows) and §12.4's 34 — with 216 values and 28 prose figures checked and agreeing. CI runs this step withcontinue-on-error.node dist/rd-gate.js— PASS, 0.00% on all eight photos.node dist/determinism-check.js— PASS (both runs agree byte for byte; the re-run reproducesresults/synthesis-window.json).encode_stdin: the eightrd:gatephotos at tiers 0–4 on their encoder-input size and tiers 0–2 on their display-size reference, 64/64 identical; the perf fixtures (gradient, noise, solid) at 100², 256², 512² and 1000², tiers 0–4 (0–2 at 512 and up), 48/48 identical.rd:gateitself cannot take a flag — its adapter stripsCHROMAHASH_TUNEby design — so identical hashes are its 0.00%.master's own build, same host,bench_stages/bench_decode_stages: within noise on every stage (figures under Summary).Coverage gaps, stated:
Tunables, so the other eight languages' vector suites exercise only the shipped path; a lever reaching them is the future change that makes it the default, and they would then run the same vectorsaccel_levers.rsalready holds every lever to.early_exit_stops_only_at_the_bound.Risks and rollout
None for callers: every flag is off in
Tunables::DEFAULT, no binding can set one, and the shipped path's bytes are pinned by the unchanged spec vectors and its timing was checked against master. Making any lever the default is a later, separate change (a patch release under §12.1), which #95's measurements are for.Issue
Closes #81. Its steps 3 and 4 — measuring on a quiet host and restating §12.1 from the measurement — are #95, which depends on this.
Decisions taken
Tunablesbool per lever, off by default, versus a bitmask field or making them unconditional now.Taken: seven
accel_*bools,falseinDEFAULT- each is its own perf arm and its own tune key, and the shipped path stays the reference every lever is timed against, as perf: measure §12.1's byte-identical levers as implemented speedups #81 asks ("behind a flag").Rejected: unconditional - would change the shipped encoder before any lever is measured on a quiet host; a bitmask - the repository's precedent (
dct_separable) is a named bool per knob, and a mask's arm labels would not say what they measure.Reverses: set the chosen fields to
trueinTunables::DEFAULT(and drop the flag once adopted).accel_quant_table.Taken: one flag - §12.1 says the two "have to land together" because the per-index width walk item 2 hoists is the key item 1's table needs.
Rejected: separate flags - would allow the index-keyed table §12.1 warns dequantizes the high band wrongly.
Reverses: split the field and gate
AcAccel::bitsanddeqon separate flags.simd/'sSimdF64.Taken: portable Rust - the compiler may map lanes to registers, and IEEE lane arithmetic does not depend on whether it does; there is no intrinsic to contract into
fmaddand no hand-written regrouping, the two hazards §12.1 names.Rejected:
SimdF64backends - four hand-written modules outside this change's scope, each carrying exactly those hazards, for a gain not yet measured against the portable form.Reverses: add a
dct_encode_lanes_with::<V: SimdF64>and dispatch it likeoklab_forward_batch.Taken: build 1–7 and 4(b)'s layout half; 4(a) included although the plan's list omitted it, since it is in §12.1 and in
decode.rs.Rejected: vectorizing decode's colour conversion now - §12.1 itself says it needs the differential tests
simd/runs for encode, which decode has no harness for.Reverses: file it as its own issue alongside a decode differential harness.
Taken: exactly as master - measured stage by stage against master's build; the fused lever carries the allocation saving, where it is attributed and timed.
Rejected: keeping the side effect - an unflagged, unmeasured change to the reference would make every lever's speedup relative to an encoder nobody ships.
Reverses: shorten the
lin_*/alpha_pixelslifetimes inanalyze's unfused branch.Taken: bind all 34 cells as
TBD, fail the gate on them, and split the quiet-host run off as perf: fill §12.4's lever speedups from a quiet-host bounded run, and restate §12.1 from them #95 - §0's rule is that wall-clock figures come from a committed run on a host that holds the 10% bar, and this host was under intermittent load (the user anticipated this deferral).Rejected: publishing this session's informal ratios - no committed run backs them, and §0 forbids an untraceable figure.
Reverses: run §0's bounded pair at this head and
verify:benchmark -- --fix.Taken: re-record, twice back to back, keeping the second only because the pair agreed within 0.4 points and 2.2% - shares are what the issue needs to size items 1, 2 and 7, and the repository treats in-process shares as readable where wall clock is not, which a discarded load-burst recording showed is true only with a guard; the totals row is kept, and lands within 0.6% of the quieter
e53e6cdrecording at 512×512.Rejected: keeping the
e53e6cdfile - itsquantize_and_packrow names a stage the recorder no longer emits, so the next re-record would break §1 without warning.Reverses:
git checkout origin/master -- tools/comparison/baselines/perf-stages.jsonand restore §1's prior rows.rd:gateat 0.00% drift" for a lever:rd:gatestripsCHROMAHASH_TUNE, so it cannot run with a flag.Taken: hash the same eight photos with all levers on and off (64/64 identical) and run
rd:gateon the default build - its figure is a function of the hash, so identical hashes are 0.00%.Rejected: teaching
rd:gateto pass a tune - it strips the variable deliberately, so no stray override can move a gated number.Reverses: add a
--tuneoption tord-gate.tsthat setsCHROMAHASH_TUNEexplicitly.Taken: restructure where possible (explicit match arms, no redundant guard, bounded
forloops, goldens from master for the re-plumbed gain/window lines) and exclude only the three that are equivalent by construction, each anchored to its line as the file's ownl_hientry is:replace < with <= in dct_encode_lanes(dct.rs:347:14, the scale floor no input lands on exactly),replace > with >= in encode_with(encode.rs:1582:19, two zero-width CfL writes at the shippedcfl_bits = 0), andreplace + with - in quantize_one(encode.rs:720:30, the symmetricac_nearestoffsets, equivalent for the same reason as the existingselect_dc_codesentry; pre-existing on master atencode.rs:526, brought into the incremental sweep by this PR's edit ofquantize_one). The third rests on an argument (no exact reconstruction-error tie on the µ-law grid betweenq - dandq + d), not on a test that asserts that premise.Rejected: unanchored patterns -
mutants.tomlwarns they silently widen an exemption.Reverses: delete the three
exclude_reentries.record_stages.py, versus exempt the baseline frombiome format.Taken: normalize in the recorder, anchored to whole numeric values - the committed file must be byte-for-byte the recorder's output and pass
format:check:compare.Rejected: a biome ignore - would stop checking a committed file.
Reverses: revert
_PADDED_EXPONENTand add the file to biome's ignore list.