feat: 0.4.0 — what it claimed, checked against what it did - #41
Merged
Conversation
The audit milestone (#33-#40). Every item was found by exercising main as a downstream consumer -- building the workspace, driving the CLI through a pty, probing the public tensor surface op by op -- and every one carries a reproduction that was run, not inferred from reading the source. Two are correctness. eigh silently symmetrized a non-symmetric input (#33): it read both triangles, averaged them, and returned the eigenpairs of (A + A^T)/2 with no error. [[1,2],[5,1]] answered [-2.5, 4.5] where the true eigenvalues are 1 +/- sqrt(10), and the returned pair did not satisfy A v = lambda v for the A that was passed. Now a typed error naming the batch index and the worst pair, above a relative tolerance chosen to keep passing what the check exists to permit: a covariance matrix assembled in f32, symmetric in intent and asymmetric in the last few bits. Nothing tied kernels.ptx to kernels.cu (#34). The crate ships the kernels twice and load_module picks between them by driver age, so a mismatch means two users on one published version run different code. The only guard compared kernel names: measured, changing relu from fmaxf(x, 0.0f) to fmaxf(x, 1.0f) passed every GPU-free suite, because the eleven tests that would catch it are ignored without hardware. A fingerprint written by the same `just ptx` that writes the PTX now fails that exact mutation. The termlens 0.9 upgrade found a third. Driving the dashboard through a pty and signalling it showed alternate_screen() still true and the cursor hidden after SIGTERM -- ratatui::init() installs a panic hook, so this was the one remaining unguarded exit (#40). SIGINT/SIGTERM/SIGHUP now restore and re-raise with the default disposition, so the process still reports as killed by that signal. Three were the project describing itself as smaller or older than it is: doctor's capability list frozen before 0.2.0 (#36), crates.io descriptions two releases behind (#37), and an undocumented f64 boundary at nn (#39). One was packaging: CUDA mandatory on every platform including macOS (#35), now an optional default-on feature that drops seven crates and a pre-main dlopen when turned off. One was a decision deferred rather than made: four reductions over an empty extent behaving four ways (#38) -- mean now errors where it returned NaN, because NaN composes into every gradient and surfaces an epoch later, while sum's and max's identities compose correctly. The design bar was that each fix leaves something behind that fails next time, and two of those guards caught their own bugs while being written: a doctor coverage check using contains() passed with eigh deleted, because "weights" contains "eigh"; and the first fingerprint wrote FNV-1a's prime with twelve hex digits instead of eleven, which the published test vectors now pin. Pins: reconverge 0.4.0 -> 0.5.0, launchbound 2.0.0 -> 2.1.0. The other two of the four did not move, so this is the one-variable case and the gate was re-measured rather than assumed: 0 findings at cc 7.5 and 8.6, 12 admitted and 0 refused at both, over-cap tile still refused with RC004 at deny confidence -- identical to the old pair. docs/research-baseline.md (e). termlens 0.8.0 -> 0.9.0, with the pty tests moved to its idioms: bin! for the spawn, snapshot_after in place of wait_until + wait_idle + screen() (one settled instant instead of three, waiting on the picture holding still rather than on bytes stopping), wait_stable after a resize, and termlens::Result<()> so a failure prints the grid. 155 tests pass, up from 146. `just ci` green, and a second CI configuration now builds --no-default-features -- which had never been compiled, which is how a mandatory CUDA backend went unnoticed for two releases. Closes #33 Closes #34 Closes #35 Closes #36 Closes #37 Closes #38 Closes #39 Closes #40 Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
Both macOS legs failed while the behaviour under test was identical on
both platforms.
The signal test asserted status.signal() == Some("Terminated"). macOS
spells it "Terminated: 15" and Linux "Terminated", so the exact-string
comparison passed on one and failed the other. contains instead — the
property is that the process still reports as killed by that signal, not
how the platform words it. Note what did NOT fail on macOS: the terminal
was restored and the cursor shown, which is the fix itself.
The no-default-features step asserted ctor was absent. It is not
CUDA-only: oxmera-metal registers at load time the same way, so on macOS
it stays in the graph and should. Now cudarc, libloading and oxmera-cuda
are checked everywhere, and ctor only where the answer is unambiguous.
Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
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.
Closes #33, closes #34, closes #35, closes #36, closes #37, closes #38, closes #39, closes #40.
The
v0.4.0audit milestone, plus the pin bumps that went with it.The two correctness fixes
#33 —
eighanswered about a matrix the caller did not pass.It read both triangles and averaged them, so it was not the lower-triangle
convention either — perturbing either triangle moved the answer. Now a typed
error naming the batch index and the worst pair, above
EIGH_SYMMETRY_TOL(
1e-5, relative) — the same bound the CPU↔GPU parity suite uses, so acovariance matrix assembled in
f32still passes. Verified across theboundary: symmetric works, noise under the bound works, noise over it is
refused, the check is scale-invariant, and a batched input names the matrix
that is actually wrong.
#34 — nothing tied the shipped PTX to the shipped source.
The crate ships the kernels twice and
load_modulechooses by driver age, soa mismatch means two users on one published version run different code. The
only guard compared kernel names. Measured:
kernels.ptx.sourcenow carries a fingerprint written by the samejust ptxthat writes the PTX. The same mutation now fails, naming the command to fix it.
The one the termlens upgrade found
#40 — driving the dashboard through a pty and signalling it:
The shell was left inside the alternate screen with the cursor hidden and
nothing written to say so.
ratatui::init()already installed a panic hook, sothis was the one remaining unguarded exit. Fixed with
signal-hook, whichcrossterm already puts in the tree — an import, not a dependency — restoring
and then re-raising with the default disposition, so the process still reports
as killed by that signal.
The rest
cudafeature;--no-default-featuresdrops 7 crates (47 → 40), 88 KB of PTX and a pre-maindlopen.Device::Cudastays a runtime error.doctor's capability list was six literals frozen before 0.2.0. Rows now live in the crates that implement them.meanover an empty extent returnedNaN; now a typed error.sum/maxkeep their identities — those compose under further reduction andNaNdoes not.nnis f32; the error saiddtype mismatch … in matmul. Now names the layer and the cast.Pins
reconverge 0.4.0 → 0.5.0, launchbound 2.0.0 → 2.1.0. The other two of the
four did not move — reconverge 0.5.0 still records
nightly-2026-04-03andcuda-oxide
a766fc26— so this is the one-variable case, and the gate wasre-measured rather than assumed:
Identical to the 0.4.0/2.0.0 result. Recorded as
docs/research-baseline.md§(e), which is explicit that only the verdicts are comparable with the
Apple-Silicon sections above it — the timings were not re-measured on this
machine.
termlens 0.9
The pty tests moved to its idioms:
bin!,snapshot_afterin place ofwait_until+wait_idle+screen()(one settled instant instead of three,and it waits on the picture holding still rather than on bytes stopping),
wait_stableafter a resize,termlens::Result<()>so a failure prints thegrid.
quit()now also asserts the terminal came back, so every dashboard testis a teardown test.
Two guards that caught their own bugs
Worth calling out, because they are the argument for the design bar:
doctorcoverage check usedreport.contains(family)and passed witheighdeleted — becauseweightscontainseigh. Now whole-word.0x1000_0000_01b3: twelve hexdigits where 1099511628211 has eleven. The published test vectors now pin it.
Verification
just ciexits 0, including the research workspace.--no-default-features: clippy clean, 10 tests pass, and a CI job assertscudarc/libloading/ctor/oxmera-cudaare absent from the graph.