Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmake/tests-submodule-pin.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@
# check's business); `--require-initialised` is the release-gate form, where a missing checkout is a failure too.
add_python_test(submodule_pin_check ${CMAKE_SOURCE_DIR}/tools/ci/submodule_pin_check.py --check --root ${CMAKE_SOURCE_DIR})
add_python_test(submodule_pin_check_self ${CMAKE_SOURCE_DIR}/tools/ci/submodule_pin_check.py --self-test)

# engine#2129: the release preflight's use of the check -- a build tree whose submodules are not the pinned commits
# BLOCKS the run, and "no checkout to compare" only WARNs so a fresh clone can still build. The function is cut out of
# scripts/weekly.sh and run against stub checkers: no submodules, no network, no build.
add_test(NAME weekly_submodule_preflight COMMAND ${CMAKE_SOURCE_DIR}/tests/weekly_submodule_preflight_test.sh)
32 changes: 32 additions & 0 deletions scripts/weekly.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ host_desc() {

# --- build / test -------------------------------------------------------------------------
build() {
# engine#2129: what this builds is $SRC, whose submodules `checkout` has just updated, and a build compiles whatever
# those work trees hold. This cannot live in preflight(): preflight runs before $SRC exists, so the check would find
# no file to run, skip, and report success -- a check that inspects nothing and says it passed. Here it is the tree
# that is about to be compiled, and the run stops before the first compile instead of after it.
preflight_submodules "$SRC/tools/ci/submodule_pin_check.py" "$SRC" || return 1
# the private components compiled in (docs/releases.md, "Private components"): the NPU runtime
# (into 1bit) and the HIP kernels (into the HRX llama.cpp), each a checkout at the commit its
# config/*.config pins; the build stops here when one cannot be fetched at that commit
Expand Down Expand Up @@ -658,6 +663,33 @@ preflight_runner_units() {
return 0
}

# The submodule row of the build (engine#2129). It runs at the top of build(), not in preflight(): the tree to inspect is
# $SRC, which `checkout` creates, and preflight runs before that. A
# build compiles whatever the submodules' work trees hold: tools/ci/submodule_pin_check.py is the check for whether those
# checkouts ARE the commits this tree pins. It already exists as a ctest row, and a ctest row runs in CI -- nothing ran it
# before a build on the box, which is where the drift was measured (7 of 7 submodules off-pin). Exit 1 stops the run: the
# build would not be of the pinned sources. Exit 2 (nothing to compare -- no submodules initialised) only WARNS, because a
# fresh clone must still be able to build and the warning says the check inspected nothing rather than implying a pass.
# --require-clean is deliberately NOT passed here: it fails on a dirty submodule work tree, and a deliberate local patch
# is a real thing a box does; the check reports those with their counts either way.
# A function of its own so tests/weekly_submodule_preflight_test.sh can run it against a stub.
preflight_submodules() {
local check=$1 src=$2 out rc=0
# An older ref does not carry the tool. That is not a failure, but it is not a pass either: say that the tree was not
# inspected, because a silent 0 here is a check that reports success for doing nothing.
[ -f "$check" ] || { echo "preflight: WARNING, not blocking: $src has no tools/ci/submodule_pin_check.py (a ref older than engine#2129), so submodule drift is not checked for this build"; return 0; }
out=$(python3 "$check" --check --root "$src" 2>&1) || rc=$?
case "$rc" in
0) printf '%s\n' "$out" | grep -v 'at their pinned commit' | sed 's/^/preflight: /' ;;
2) printf '%s\n' "$out" | sed 's/^/preflight: /'
echo "preflight: WARNING, not blocking: no submodule checkout to compare against the pin, so this build follows whatever the work trees hold (tools/ci/submodule_pin_check.py exit 2; git submodule update --init fills it)" ;;
*) printf '%s\n' "$out" | sed 's/^/preflight: /'
echo "preflight: a submodule is not at the commit this tree pins, so the build would not be of the pinned sources (tools/ci/submodule_pin_check.py exit $rc; the line above names the submodule and what to do)"
return 1 ;;
esac
return 0
}

# --- preflight ----------------------------------------------------------------------------
# What the stages need from the machine, checked before the first one runs, so that a missing
# tool stops the run at the start rather than after an hour of build (2026-10-19 rehearsal).
Expand Down
76 changes: 76 additions & 0 deletions tests/weekly_submodule_preflight_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Copyright 2026 bong-water-water-bong
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# weekly_submodule_preflight_test.sh: the release preflight BLOCKS when a submodule is not the commit the tree pins and
# only WARNS when there is no checkout to compare against (engine#2129). The function is cut out of scripts/weekly.sh and
# run against stub checkers, so no submodules, no network and no build.
set -u
root=$(cd "$(dirname "$0")/.." && pwd)
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
fail=0
ok() { printf 'ok %s\n' "$1"; }
bad() { printf 'FAIL %s\n' "$1"; fail=1; }

sed -n '/^preflight_submodules() {$/,/^}$/p' "$root/scripts/weekly.sh" > "$tmp/fn.sh"
grep -q 'submodule_pin_check.py exit' "$tmp/fn.sh" || { bad "could not cut preflight_submodules() out of scripts/weekly.sh"; echo "weekly_submodule_preflight: FAIL"; exit 1; }
grep -q 'preflight_submodules "' "$root/scripts/weekly.sh" || bad "scripts/weekly.sh's preflight no longer calls preflight_submodules"
say() { echo "$*"; }
# shellcheck disable=SC1091
. "$tmp/fn.sh"

# A stub standing in for tools/ci/submodule_pin_check.py. It is a Python file because the function runs the real tool as
# `python3 <path>`: printing what the tool prints and exiting with the code under test is the whole stub.
stub() { # <exit code> <line>
{
printf 'import sys\nprint('
printf '%s' "$2" | sed 's/.*/"&"/'
printf ')\nsys.exit(%s)\n' "$1"
} > "$tmp/stub.py"
echo "$tmp/stub.py"
}
row() { # <name> <want return> <want text> <rc> <line>
local name=$1 want=$2 text=$3 rc=$4 line=$5
local check out got=0
check=$(stub "$rc" "$line")
out=$(preflight_submodules "$check" "$tmp" 2>&1) || got=$?
if [ "$got" = "$want" ] && printf '%s\n' "$out" | grep -q -- "$text"; then
ok "preflight: $name -> return $got"
else
bad "preflight: $name: want return $want with '$text', got $got: $out"
fi
}

# A passing run prints nothing at all: the tool's summary line ("N submodule(s) at their pinned commit") is the one
# thing the function drops, because a preflight that always prints a line trains its reader to skip the lines.
check=$(stub 0 "submodule_pin: 7 submodule(s) at their pinned commit")
out=$(preflight_submodules "$check" "$tmp" 2>&1)
if [ -z "$out" ]; then ok "a tree whose submodules are their pins passes silently"; else bad "a passing run should print nothing, got: $out"; fi
row "a submodule at a different commit BLOCKS the run" 1 "not at the commit this tree pins" 1 "submodule_pin: third_party/x is checked out at abc, which is NOT the commit this tree pins"
row "no checkout to compare only WARNS" 0 "WARNING, not blocking" 2 "submodule_pin: no submodules reported - cannot tell, and that is not a pass"

# A dirty submodule at its pin is reported and does NOT block: a deliberate local patch is a real thing a box does.
row "a dirty submodule at its pin is reported, not blocking" 0 "changed path" 0 "submodule_pin: third_party/x is at the pinned abc but its work tree has 301 changed path(s)"

# A checker that is not there at all (an older ref): not a failure, but it must SAY that nothing was inspected --
# a silent pass here is a check that reports success for doing nothing, which is what its first version did.
out=$(preflight_submodules "$tmp/no-such-check.py" "$tmp" 2>&1) && printf '%s' "$out" | grep -q 'is not checked for this build' && ok "a missing checker warns and does not stop the run" || bad "a missing checker should warn (and not stop the run): $out"

# The asymmetry, in one assertion: exit 2 must not block, exit 1 must.
check=$(stub 2 "submodule_pin: cannot tell")
if preflight_submodules "$check" "$tmp" >/dev/null 2>&1; then ok "exit 2 is not a block"; else bad "exit 2 should not block the release"; fi

if [ "$fail" != 0 ]; then echo "weekly_submodule_preflight: FAIL"; exit 1; fi
echo "weekly_submodule_preflight: ok"
27 changes: 25 additions & 2 deletions tools/ci/hrx_f16_narrowing.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,24 @@
"""
import argparse
import pathlib
import re
import sys

CORPUS = pathlib.Path("third_party/llama.cpp/ggml/src/ggml-hrx/kernel-corpus/kernels")
BASELINE = pathlib.Path("tools/ci/hrx_f16_narrowing.txt")
NARROW = ("f32 to f16", "truncf")
# The narrowing this lists is the *type pair*, not the opcode spelling: the corpus writes the scalar form as
# `%x = scalar.fptrunc %y : f32 to f16` and the vector form as
# `%h = vector.fptrunc %v : vector<4xf32> to vector<4xf16>`, and a substring test on `f32 to f16` saw only the first
# (engine#2219). Measured at the pin 2b7960bae: 233 `fptrunc` lines, 101 scalar and 132 vector, over 27 more bodies than
# the old rule reached.
#
# The bare `truncf` alternative it used to carry is GONE, and that is a correction rather than a simplification: it
# matched `loom-libs/motifs/unary_f32_apply.loom`, whose narrowing spells out `scalar.truncf %value : f32` and
# `vector.truncf %values : vector<4xf32>` and names no f16 type at all -- a truncation *within* f32, listed as a
# narrowing to f16 for as long as the ratchet has existed. No body in this corpus spells an f16 narrowing as a
# `truncf` that these two patterns miss (checked), so dropping it removes exactly that line and nothing else: 25 -> 51,
# with the 52nd body in the old count being the one that was never narrowing to f16.
NARROW = (re.compile(r"f32 to f16"), re.compile(r"vector<\d+xf32> to vector<\d+xf16>"))
GUARD = ("scale_log2", "prescale")


Expand All @@ -59,7 +72,7 @@ def narrowing_bodies(corpus):
found = set()
for path in corpus.rglob("*.loom"):
body = path.read_text(encoding="utf-8", errors="replace")
if any(marker in body for marker in NARROW):
if any(m.search(body) if hasattr(m, "search") else m in body for m in NARROW):
found.add(path.relative_to(corpus).as_posix())
return found

Expand Down Expand Up @@ -144,6 +157,16 @@ def check_that(ok, what):
new, stale = drift(baseline_paths((root / BASELINE).read_text()), narrowing_bodies(corpus))
check_that(stale == ["a.loom"],
"a rename leaves a stale baseline line (the engine#1728 lesson)")
# engine#2219: the vector form narrows too, and a truncation that stays inside f32 does not.
(corpus / "v.loom").write_text(" %h = vector.fptrunc %v : vector<4xf32> to vector<4xf16>\n")
check_that(narrowing_bodies(corpus) >= {"v.loom"}, "the vector form is a narrowing (it was invisible before #2219)")
(corpus / "t.loom").write_text(
" %a = scalar.truncf %value : f32\n %b = vector.truncf %values : vector<4xf32>\n")
check_that("t.loom" not in narrowing_bodies(corpus),
"a truncf that names no f16 type is NOT a narrowing (the body the old `truncf` alternative listed)")
check_that("v.loom" in narrowing_bodies(corpus) and "t.loom" not in narrowing_bodies(corpus),
"... and the two are decided by the type pair, not by which opcode is spelled")
(corpus / "v.loom").unlink(); (corpus / "t.loom").unlink()
(corpus / "renamed.loom").write_text(" %x = scalar.fptrunc %y : f32 to f16 # scale_log2\n")
check_that(guarded({"renamed.loom"}, corpus) == {"renamed.loom"},
"a guard marker is reported, not denied")
Expand Down
28 changes: 27 additions & 1 deletion tools/ci/hrx_f16_narrowing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,54 @@
# perfectly correct. What the list buys is that a NEW narrowing body cannot appear without someone
# writing it down and saying what feeds it -- and that is the open question on engine#1962: which of
# these are protected by a producer-side prescale cannot be answered by reading them.
loom-libs/motifs/bf16_f16.loom
loom-libs/motifs/dequant.loom
loom-libs/motifs/dequant_1bit.loom
loom-libs/motifs/dequant_prism.loom
loom-libs/motifs/f32_f16.loom
loom-libs/motifs/llm_attention_qkv_matmul_postops.loom
loom-libs/motifs/mul_mat_f32_f32_wmma_core.loom
loom-libs/motifs/mul_mat_id_f16_f16_wmma_core.loom
loom-libs/motifs/mul_mat_id_f32_f32_wmma_core.loom
loom-libs/motifs/mul_mat_id_q4k_f16_wmma_projection.loom
loom-libs/motifs/mul_mat_id_q5k_f16_wmma_projection.loom
loom-libs/motifs/mul_mat_id_q6k_f16_wmma_projection.loom
loom-libs/motifs/mul_mat_id_swiglu_f16_f16_accumulate.loom
loom-libs/motifs/mul_mat_id_swiglu_f16_f16_wmma_body.loom
loom-libs/motifs/mul_mat_quantized_f16_prefill.loom
loom-libs/motifs/publish_f32.loom
loom-libs/motifs/q4_k_f16.loom
loom-libs/motifs/q6_k_f16.loom
loom-libs/motifs/q8_0_f16.loom
loom-libs/motifs/q8_1_f16.loom
loom-libs/motifs/quantize_q8_1_x4.loom
loom-libs/motifs/unary_f32_apply.loom
loom-libs/ops/copy_f32.loom
loom-libs/ops/flash_attention_decode_split_f32_f16_wmma.loom
loom-libs/ops/flash_attention_f32_f16_wmma.loom
loom-libs/ops/gated_delta_net_f32_wmma.loom
loom-libs/ops/llm_attention_k_matmul_rope_set_rows_decode_f32_f32.loom
loom-libs/ops/llm_attention_v_matmul_set_rows_decode_f32_f32.loom
loom-libs/ops/mul_mat_id_swiglu_f32_f32_wmma.loom
loom-libs/ops/mul_mat_q6_k_packed_f16_wmma.loom
loom-libs/ops/mul_mat_swiglu_f32_f32_wmma.loom
loom-libs/ops/mul_mat_swiglu_symmetric_i4_wmma.loom
loom-libs/ops/mul_mat_symmetric_i4_wmma.loom
loom-libs/ops/mul_mat_symmetric_i8_wmma.loom
loom-libs/ops/rmsnorm_binary_f32.loom
loom-libs/ops/rope_set_rows_f32.loom
loom-libs/ops/set_rows.loom
loom-libs/ops/small_rows_f32.loom
qwen_moe/ggml/linear_q6k_q8_1_x4.loom
qwen_moe/ggml/quantize_q8_1_x4.loom
qwen_moe/qwen3_moe/attention_postprocess_f32_f16.loom
qwen_moe/qwen3_moe/attention_prepare_quantized.loom
qwen_moe/qwen3_moe/dense_linear_quantized_f16_wmma.loom
qwen_moe/qwen3_moe/flash_attention_decode_f32_f16_wmma.loom
qwen_moe/qwen3_moe/flash_attention_decode_q128_f32_f16_wmma.loom
qwen_moe/qwen3_moe/flash_attention_decode_split_f32_f16_wmma.loom
qwen_moe/qwen3_moe/flash_attention_decode_split_next_q8_test.loom
qwen_moe/qwen3_moe/flash_attention_f32_f16_wmma.loom
qwen_moe/qwen3_moe/routed_down_quantized_f16_wmma.loom
qwen_moe/qwen3_moe/routed_down_weighted_reduce_next_rmsnorm_q8_1_x4.loom
qwen_moe/qwen3_moe/routed_gate_up_swiglu_q4k.loom
qwen_moe/qwen3_moe/routed_linear_q4k_f16_wmma.loom
Loading
Loading