diff --git a/CMakeLists.txt b/CMakeLists.txt index e60f43ad6..da803dd99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,14 +210,16 @@ add_dependencies(onebit npu_decode_note_test) # refusal this pre-empts ("differ in size") needed private-repo archaeology to diagnose; the symptom is checkable here. add_test(NAME kernel_set_check COMMAND bash ${CMAKE_SOURCE_DIR}/tests/kernel_set_check_test.sh) -# engine#2186/#2244: two checks that were only ever run by hand. `tools/ci/dupdef_check.py` is the one +# engine#2186/#2244: `tools/ci/dupdef_check.py`, a check that was only ever run by hand. It is the one # that found a shadowed `def self_test()` in tools/hrx_f16_narrowing_check.py -- the surviving copy -# passed and the shadowed one's arms could never fail. Both take no build input, so a ctest row is the +# passed and the shadowed one's arms could never fail. It takes no build input, so a ctest row is the # route that needs no ci.yml line (the workflow is not editable from a lane). +# engine#2246 also added a row for that file's --self-test; `add_python_test(hrx_f16_narrowing ...)` +# at line 418 has run exactly that since #2204, so the row was a duplicate and is gone -- one script +# gets one row. add_test(NAME dupdef_check COMMAND python3 ${CMAKE_SOURCE_DIR}/tools/ci/dupdef_check.py --check WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) -add_test(NAME hrx_f16_narrowing_selftest COMMAND python3 ${CMAKE_SOURCE_DIR}/tools/hrx_f16_narrowing_check.py --self-test - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + add_test(NAME bump_hrx_report COMMAND bash ${CMAKE_SOURCE_DIR}/tests/bump_hrx_report_test.sh ${CMAKE_SOURCE_DIR}/scripts/bump-hrx-report.sh) add_test(NAME metrics_serve COMMAND ${CMAKE_SOURCE_DIR}/tests/metrics_serve.sh $) # serve --print-backend: the llama-server Lemonade starts itself, one hop (docs/serve.md): no GPU. diff --git a/scripts/pack-elf.sh b/scripts/pack-elf.sh index e61cecc80..d7cca3d80 100644 --- a/scripts/pack-elf.sh +++ b/scripts/pack-elf.sh @@ -108,22 +108,6 @@ pack_unresolved_needed() { # (a hardware-gate cache, a drop) carries that tree's RUNPATH and its build's missing parts: in the 2026-10-23 # rc, `llama-perplexity` came from a hardware-gate build and pointed at that build's directory. Compares each # staged file with the same relative path under the build and reports what has no counterpart there. -pack_require_provenance() { # - local stage=$1 build=$2 f rel bad=0 - while IFS= read -r -d '' f; do - pack_is_elf "$f" || continue - rel=${f#"$stage"/} - if [ ! -f "$build/$rel" ]; then - echo " $rel is not in the build being packaged" - bad=1 - elif ! cmp -s "$f" "$build/$rel"; then - echo " $rel differs from the build being packaged" - bad=1 - fi - done < <(find "$stage" -type f -print0) - return $bad -} - # The programs `put` was asked for are the ones the release promises: if one of them cannot resolve, that is a # build problem (the tool came from another tree, or its implementation library was not built) and the package # has to fail rather than quietly lose the tool. Call once per directory group: pack_require_tools diff --git a/tools/ci/hrx_f16_narrowing.py b/tools/ci/hrx_f16_narrowing.py index 434fa081a..d110d7ed4 100755 --- a/tools/ci/hrx_f16_narrowing.py +++ b/tools/ci/hrx_f16_narrowing.py @@ -41,6 +41,7 @@ hrx_f16_narrowing.py --write rewrite the baseline from the corpus """ import argparse +import hashlib import pathlib import re import sys @@ -57,6 +58,22 @@ # Measured (engine#2167): at 120 lines past the init, `mul_mat_id_f16_f16_wmma_core`'s four accumulators read as # unaffected, because their update sits ~250 lines below in a long Q4_K/Q6 loop body. 250 finds them. ACC_WINDOW = 250 + + +def corpus_fingerprint(corpus): + """sha256 over the corpus: sorted (relative path, bytes), so a moved file changes it too. + + A narrowing count is only comparable to another run if the tree it read is identified. Two + checkouts of third_party/llama.cpp at different commits both answer "9 bodies" until one of them + does not, and without this the only way to tell is to read the submodule sha by hand. + """ + h = hashlib.sha256() + n = 0 + for f in sorted(corpus.rglob("*.loom")): + h.update(str(f.relative_to(corpus)).encode()) + h.update(hashlib.sha256(f.read_bytes()).digest()) + n += 1 + return f"{h.hexdigest()[:16]} ({n} .loom file(s))" # The vector form is most of the corpus: `vector.fptrunc %x : vector<4xf32> to vector<4xf16>` contains neither # "f32 to f16" nor "truncf", and at llama.cpp 2b7960bae 132 of the 233 narrowing lines are written that way. # Matching the scalar spelling alone listed 25 bodies; the corpus has 51 (engine#2204's review). A bare `truncf` is @@ -132,11 +149,14 @@ def drift(baseline, found): return sorted(found - baseline), sorted(baseline - found) -def check(root): - corpus = root / CORPUS +def check(root, corpus=None): + corpus = pathlib.Path(corpus) if corpus else root / CORPUS if not corpus.is_dir() or not any(corpus.rglob("*.loom")): - print(f"skip: no .loom corpus at {CORPUS} (check out third_party/llama.cpp)", file=sys.stderr) + print(f"skip: no .loom corpus at {corpus} (check out third_party/llama.cpp, or pass --corpus)", file=sys.stderr) return 77 + # Printed before the verdict, not after it: a FAIL is when the identity of the tree matters most. + print(f"CORPUS {corpus.resolve()}") + print(f"FINGERPRINT {corpus_fingerprint(corpus)}") base_text = (root / BASELINE).read_text(encoding="utf-8") baseline = baseline_paths(base_text) found = narrowing_bodies(corpus) @@ -180,10 +200,10 @@ def check(root): return 0 -def write(root): - corpus = root / CORPUS +def write(root, corpus=None): + corpus = pathlib.Path(corpus) if corpus else root / CORPUS if not corpus.is_dir(): - print(f"skip: no .loom corpus at {CORPUS}", file=sys.stderr) + print(f"skip: no .loom corpus at {corpus}", file=sys.stderr) return 77 head = "".join(line for line in (root / BASELINE).read_text(encoding="utf-8").splitlines(True) if line.lstrip().startswith("#")) @@ -280,6 +300,18 @@ def check_that(ok, what): " %next = vector.mulf %acc, %k : vector<16xf16>\n") check_that("far.loom" in accumulator_bodies(accdir), "an update 200 lines below the init is found") + # The corpus identity: same bytes -> same fingerprint, an edit -> a different one, a move -> a + # different one (the relative path is hashed), and the count it reports. + with tempfile.TemporaryDirectory() as d: + c = pathlib.Path(d) + (c / "a.loom").write_text("one\n"); (c / "s").mkdir(); (c / "s" / "b.loom").write_text("two\n") + fp = corpus_fingerprint(c) + check_that(fp.endswith("(2 .loom file(s))"), "fingerprint: counts the .loom files it read") + check_that(fp == corpus_fingerprint(c), "fingerprint: the same corpus twice is the same value") + (c / "s" / "b.loom").write_text("two changed\n") + check_that(fp != corpus_fingerprint(c), "fingerprint: a one-byte edit changes it") + (c / "a.loom").rename(c / "s" / "a.loom") + check_that(fp != corpus_fingerprint(c), "fingerprint: a move changes it too (the path is hashed)") print(f"hrx_f16_narrowing self-test: {'ok' if not failures else f'{failures} failure(s)'}") return 1 if failures else 0 @@ -290,14 +322,27 @@ def main(): ap.add_argument("--write", action="store_true") ap.add_argument("--self-test", action="store_true") ap.add_argument("--root", default=".") + ap.add_argument("--corpus", default=None, + help="the .loom corpus to scan (default: the pinned llama.cpp checkout's kernel-corpus)") args = ap.parse_args() root = pathlib.Path(args.root).resolve() + corpus = pathlib.Path(args.corpus).resolve() if args.corpus else None if args.self_test: return self_test() if args.write: - return write(root) + # The baseline is a file in the tree it describes, so rewriting it from a corpus that lives + # somewhere else records a corpus this checkout does not have -- and the next --check then + # reports every one of its own bodies as "in the baseline but no longer narrows". Refused + # rather than warned, because the damage is a committed baseline that no longer describes + # this tree (found by doing exactly that with a two-line fixture corpus). + if corpus is not None and root not in corpus.parents and corpus != root: + print(f"hrx_f16_narrowing: refusing --write with --corpus {corpus} outside --root {root}: " + f"the baseline is written into the root and would describe a corpus this tree does not have", + file=sys.stderr) + return 2 + return write(root, corpus) if args.check: - return check(root) + return check(root, corpus) ap.print_help() return 2