Repository navigation
bench-preflight: a short correct -lm mmap run is not refused (answers the open review on #1913) - #2193
Conversation
…ints it (engine #1668) The `--dispatch` path pins `-lm`, validates it against llama-bench's own set, prints it in the header, and has arms asserting the flag reached the bench's argv. Nothing checked that the run **did** it: a bench that ignored `-lm` (the flag is a default, not a contract) produced a tally the header then mislabelled, which is the same defect the pin exists to prevent — one variable measured, two reported. llama.cpp puts a model it mmaps in `/proc/<pid>/maps` and a model it only reads is not there, so the mode is observable rather than trusted. The bench now runs in the background and the preflight polls that process's maps while it lives; a mode whose mapping disagrees with the flag is refused: -lm none that actually mmapped is refused rc=1 -lm mmap that only read is refused rc=1 observed mmap agrees with -lm mmap rc=0 observed read agrees with -lm none rc=0 Only the unambiguous modes are asserted (`mmap`/`mmap+mlock` map, `none`/`dio` do not); `mlock` alone is left unasserted because whether it maps depends on the loader, and a gate that guessed would fail a correct run. An observed mapping is trusted on its own, but an absent one only when the process was seen alive after the sample — a bench that maps and exits between polls must not be reported as a mismatch, and if it finishes before any sample the line is a WARN, not a failure. Test: five arms in tools/tests/test-bench-preflight.sh driving the real script against the fake world, with a fake bench that really maps or really reads the model (FAKE_BENCH_LM makes it disagree with `-lm`). 21 passed, 0 failed; against the unpatched script the three mismatch/reason arms fail and the two agreement arms still pass, which is what makes them controls rather than decoration. Refs #1668
…ot (engine #1668) The load-mode observation runs the bench as a background child, and nothing ended it when the preflight was signalled: the review of #1913 at d098635 showed the child surviving a SIGTERM to the script. The EXIT trap now kills the bench and waits for it before removing the log, and HUP/INT/TERM exit through it. Two arms signal the preflight while a 30 s fake bench runs and assert its pid is gone: with the fix 23 passed, 0 failed; with tools/bench-preflight.sh as at d098635 both arms fail ('bench pid ... is still running'). Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…nalled again (review of #1939) The EXIT trap sent TERM and then waited with no deadline: a bench that ignores TERM hung the trap until the bench ended by itself, and a ticket killing the shell in that window leaves the orphan the trap exists to prevent. It now sends TERM, gives the bench at most 5 s (a zombie counts as gone), sends KILL and reaps. bench_pid is emptied once the bench is reaped, so the trap never signals a pid the system has reused, and the trap reads it as ${bench_pid:-}. New arm: a fake bench that ignores SIGTERM. With the fix 24 passed, 0 failed; with the script as at 2b39e6a the arm fails ('the preflight took 30s to return'). Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ping is matched by resolved path Absence of the model from /proc/<pid>/maps counted on any poll where the bench was alive, so a bench that loads, maps, runs and exits inside one poll interval was refused although it honoured -lm mmap (review of #1913 at 7b218e0, 5 of 5). Absence now needs three samples taken while the bench was already dispatching and still running; short of that the existing WARN is printed and the tally goes ahead. The mapping is matched by realpath of the model as a whole pathname instead of by basename. Four arms; each fails on the script before this change. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
ready for review at faf50e6 |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at faf50e6: READY
"A short correct -lm mmap run is not refused" is the right refinement of the preflight: a run that is short and correct is evidence the path works, and treating it as too-short-to-judge would make the preflight refuse the very configuration it is meant to certify.
The stop_bench() trap with bench_pid is the cleanup #1939 established — a preflight whose stop can hang is a preflight that leaves a bench running, which is the failure the hold-ticket rule exists to prevent. Returning early when there is no PID ([ -n "${bench_pid:-}" ] || return 0) keeps "nothing to stop" out of the error path.
The pair of this and #2195 is what the bench harness needed: one makes a benchmark row survive a runner without a device, the other stops the preflight refusing correct short runs. Both were producing noise that read as PR defects.
|
VERDICT: READY at Both objections are answered by changing what counts as evidence, which is the right fix rather than a looser threshold. "Absence needs evidence" — a sample only counts as absent if the bench was already printing dispatch lines before The residual case is stated rather than hidden: a bench whose whole mapped life fits between two polls and then stops is left as judged not worth a slower gate. That is the honest trade — a gate that polls fast enough to catch it costs every run — and writing it down is what stops someone re-opening it as a bug. One ask: the WARN path should say which condition produced it (never observed vs observed-but-not-mapped), because a lane reading one WARN line in a log will otherwise re-run and hope. The suite already distinguishes them internally; the line is what travels. Scope of my check: fresh configure (rc 0) and |
Refs #1668 (closed). Follow-up to #1913 and #1939: this branch is
main+ #1913 (7b218e0) + #1939 (e122b84) + one commit that answers the objection still open on #1913.The objection (review of #1913 at
7b218e0, 04:40Z, unanswered)-lm mmaprun is refused, 5 of 5. The bench is alive and unmapped while it loads, then maps, runs and exits inside one 0.1 s poll, so no sample ever holds the mapping, and the old rule counted any "alive, not mapped" poll as proof of absence.(The third point of that review, the bench outliving a signalled preflight, is #1939's fix and is carried here unchanged.)
What changes
/proc/<pid>/mapswas read and was still running after the read. Three such samples refuse a-lm mmaprun; fewer print the existingWARN ... not checkedand the tally goes ahead with exit 0.realpathof the model, compared as a whole pathname. A same-named file elsewhere no longer matches; a model given through a symlink now does (before, it was refused as "not mapped").mlockis still not asserted.One residual case is left as it was judged not worth a slower gate: a bench whose whole mapped life fits between two polls and which then stays alive, unmapped, for 0.2 s more would still be refused.
Proof
Four new arms in
tools/tests/test-bench-preflight.sh(ctest rowbench_preflight, already registered). No GPU, model or hold is used: the test fakes the box.At this head:
The same test file against the script as it is on #1913 + #1939 (before this commit):
Two mutants of the new script, each caught by one arm:
FAIL a short correct -lm mmap run passes 0 of 5, 27 passed, 1 failedFAIL a same-named file elsewhere is not it rc=1, 27 passed, 1 failedMerging
This PR contains #1913 and #1939 whole, so merging it makes both redundant; they can then be closed. Diff against
mainis two files:tools/bench-preflight.shandtools/tests/test-bench-preflight.sh.🤖 Generated with Claude Code