Foundry/tt/tools/check_tensix_reduce_exit_st...

217 lines
9.9 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 David (Corey) Herrera
"""
check_tensix_reduce_exit_state.py
Enforces the two source-level invariants that keep the REDUCE family's exit state safe.
Both were settled ON SILICON on 2026-08-13 (cores 60-63), each with its own red, and both
are invisible to every other gate in this tree: `diff_tensix_kernel_instructions.sh` is
perfectly happy with either violation -- one of them REMOVES instructions and the other
removes two stores that no reduce kernel issues -- and `size` cannot see them either.
Usage: check_tensix_reduce_exit_state.py [kernels-dir] (default ../kernels)
Needs: nothing. No card, no toolchain, no tt-metal source.
---------------------------------------------------------------------------
INVARIANT 1 -- a kernel that programs the reduce packer mask must clear it
---------------------------------------------------------------------------
`ft_tensix_pack_reduce_row_mask_config()` makes the packer write ONE datum per row and
ZERO everywhere else. `ft_tensix_pack_reduce_mask_clear()` is the ONLY
thing that undoes it -- the stock reduce uninit has an empty body and the UNPACK side
of `reduce_uninit` has no statement at all -- so EVERY pack issued between the two is
masked, whether or not it belongs to the reduce.
MEASURED: deleting the single `ft_tensix_pack_reduce_mask_clear()` line from
`rms_norm_compute.c` -- nothing else; trisc0/trisc1/brisc/ncrisc stay byte-identical and
trisc2 shrinks 2548 -> 2500 B -- takes rms_norm from PCC 0.999978735 / max-abs 0.034142 to
**PCC 0.195355069 / max-abs 3.65988**, because the second half of the kernel,
`y = (x*gamma)*scale`, then packs through the reduce mask.
The count is WRAPPER-AWARE: a same-file `static` helper whose body contains the clear
(`attention_reduce_uninit()` in `resident_attention_compute.c`) counts as a clear at each
of ITS call sites. A flat grep reports that kernel as a violation and it is not one.
---------------------------------------------------------------------------
INVARIANT 2 -- ft_tensix_pack_hw_configure() must re-establish the packer baseline
---------------------------------------------------------------------------
The mask state is per-core and outlives the program. What stops it poisoning the NEXT
kernel image is exactly two full-word writes in `ft_tensix_pack_hw_configure()`:
cfg[PCK_EDGE_OFFSET_SEC0_mask] = 0xffff /* mask through, selectors 0 */
cfg[TILE_ROW_SET_MAPPING_0_row_set_mapping_0] = 0x0 /* every row -> row set 0 */
Only SEC0 carries the four `tile_row_set_select_packN` bits, so zeroing them sends every
packer row back to TILE_ROW_SET_MAPPING_0, which the second line zeroes. The stale
`PCK_EDGE_OFFSET_SEC1 = 0x0001` (cfg 25) and `TILE_ROW_SET_MAPPING_1 = 0x55555555`
(cfg 21) a reduce leaves behind become UNREACHABLE rather than merely stale. Neither cfg
25 nor cfg 21 is written by the startup path, upstream or here.
MEASURED as a 2x2, on cores the experiment had never touched, same process and same core
with no reset and no re-boot between the two ops:
leaky rms_norm + stock matmul -> matmul PCC 0.999998638 (repaired)
good rms_norm + no-repair matmul -> matmul PCC 0.999998638 (mutation alone
is harmless)
leaky rms_norm + no-repair matmul -> matmul PCC 0.24-0.26 on ALL SEVEN shapes,
max-abs to 22.6, 8 checks failed
So the poison is real, those two lines are what pay for it, and deleting them is a silent
4x accuracy loss on the next op rather than a build error. This script is that build error.
WHAT THIS DELIBERATELY DOES NOT DO: it changes no emitted instruction, and it does not
"fix" upstream. Upstream really does leave MATH's ADDR_MOD_0..3, its MOP template, its
replay slots 16-23, UNPACK's haloize bit, both unpackers' X-end and the UNPACK MOP at
replay slots 0-1 live past `reduce_uninit`, and we reproduce that faithfully. Every one of
those is reprogrammed by the next op's own init, which is why they are a documented hazard
and not a check. The packer mask is the one an op init does NOT reprogram, so it is the one
that gets a gate.
Status: both invariants proven load-bearing on silicon 2026-08-13; both violations
reproduced red by this script.
Issues: None
Todo: None
"""
import re
import sys
from pathlib import Path
MASK_CONFIG = "ft_tensix_pack_reduce_row_mask_config"
MASK_CLEAR = "ft_tensix_pack_reduce_mask_clear"
PACK_HEADER_NAME = "ft_tensix_compute_pack.h"
BASELINE_WRITES = (
("PCK_EDGE_OFFSET_SEC0_mask <- 0xffff",
re.compile(r"cfg\[FT_BH_CFG_PACKER_EDGE_MASK0_WORD\]\s*=\s*0xffffu?\s*;"),
"a reduce that exits with its mask set will poison the next kernel image on that core"),
("TILE_ROW_SET_MAPPING_0 <- 0x0",
re.compile(r"cfg\[FT_BH_CFG_PACKER_TILE_ROW_MAP0_WORD\]\s*=\s*0x0u?\s*;"),
"the stale TILE_ROW_SET_MAPPING_1 = 0x55555555 a reduce leaves behind becomes reachable"),
)
def strip_comments(text):
"""Remove /* */ and // comments, preserving line count so reported lines stay true."""
def blank_but_keep_newlines(match):
return re.sub(r"[^\n]", " ", match.group(0))
text = re.sub(r"/\*.*?\*/", blank_but_keep_newlines, text, flags=re.S)
text = re.sub(r"//[^\n]*", lambda m: " " * len(m.group(0)), text)
return text
def helper_functions_containing(text, callee, excluding):
"""Names of same-file functions whose body calls `callee` but never `excluding`.
A reduce kernel may wrap the clear in a local `reduce_uninit`-shaped helper and call it
from several places; those call sites are clears too. Bodies are delimited by a brace at
column 0, which is this tree's C style (best-practices: K&R, function brace on its own
line at column 0).
`excluding` is the mask CONFIG. Without it `kernel_main` itself qualifies as a "helper"
-- it contains the clear -- and its whole body is then skipped, hiding the very call the
check is counting. That made `rms_norm_compute.c`, the one kernel this was written for,
report a false violation.
"""
names = []
definition = re.compile(r"^(?:static\s+)?[A-Za-z_][\w \t*]*?\b(\w+)\s*\([^;]*?\)\s*$",
re.M)
lines = text.split("\n")
for index, line in enumerate(lines):
match = definition.match(line)
if not match or index + 1 >= len(lines) or lines[index + 1].rstrip() != "{":
continue
end = index + 2
while end < len(lines) and lines[end] != "}":
end += 1
body = "\n".join(lines[index + 2:end])
if callee + "(" in body and excluding + "(" not in body:
names.append((match.group(1), index + 1, end + 1))
return names
def count_calls(text, name, skip_ranges=()):
"""Call sites of `name(`, ignoring lines inside `skip_ranges` (1-based, inclusive)."""
hits = []
for index, line in enumerate(text.split("\n"), start=1):
if name + "(" not in line:
continue
if any(start <= index <= end for start, end in skip_ranges):
continue
hits.append(index)
return hits
def check_kernel_source(path):
"""Returns (checked, failure_message_or_None, info)."""
text = strip_comments(path.read_text())
configs = count_calls(text, MASK_CONFIG)
if not configs:
return False, None, None
helpers = helper_functions_containing(text, MASK_CLEAR, excluding=MASK_CONFIG)
skip = [(start, end) for _, start, end in helpers]
clears = count_calls(text, MASK_CLEAR, skip_ranges=skip)
for helper_name, _, _ in helpers:
clears += count_calls(text, helper_name, skip_ranges=skip)
detail = "%d config(s) at %s, %d clear(s) at %s" % (
len(configs), ",".join(str(n) for n in configs),
len(clears), ",".join(str(n) for n in sorted(clears)))
if helpers:
detail += " (via %s)" % ", ".join(name for name, _, _ in helpers)
if len(clears) < len(configs):
return True, "%s programs the reduce packer mask more often than it clears it -- %s" % (
path.name, detail), None
return True, None, "%s: %s" % (path.name, detail)
def main():
kernels = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parent.parent / "kernels"
failures = 0
checks = 0
print("=== invariant 1: every kernel that programs the reduce packer mask clears it")
sources = sorted(kernels.glob("*.c"))
if not sources:
print(" FAIL: no kernel sources under %s" % kernels)
failures += 1
for source in sources:
checked, failure, info = check_kernel_source(source)
if not checked:
continue
checks += 1
if failure:
print(" FAIL: " + failure)
failures += 1
else:
print(" ok: " + info)
if checks == 0:
print(" FAIL: no kernel under %s programs the reduce packer mask -- this check "
"cannot fail, which makes it worthless" % kernels)
failures += 1
print("=== invariant 2: ft_tensix_pack_hw_configure re-establishes the packer baseline")
header = kernels / PACK_HEADER_NAME
if not header.is_file():
print(" FAIL: %s does not exist" % header)
failures += 1
else:
body = strip_comments(header.read_text())
for label, pattern, consequence in BASELINE_WRITES:
checks += 1
if pattern.search(body):
print(" ok: %s is present" % label)
else:
print(" FAIL: %s no longer writes %s -- %s" % (PACK_HEADER_NAME, label,
consequence))
failures += 1
print("=== %d check(s), %d failure(s)" % (checks, failures))
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())