Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6377caa
test(tensilelite): strengthen LibraryIO parse contracts
davidd-amd Sep 10, 2026
f8380f7
test(tensilelite): strengthen LibraryIO serialization contracts
davidd-amd Sep 10, 2026
9b1ee50
test(tensilelite): strengthen Configuration contracts
davidd-amd Sep 10, 2026
7d54a1b
test(tensilelite): strengthen Naming contracts
davidd-amd Sep 10, 2026
5174cca
test(tensilelite): strengthen BenchmarkSplitter contracts
davidd-amd Sep 10, 2026
5b1c63a
test(tensilelite): strengthen Problem range contracts
davidd-amd Sep 10, 2026
7943b30
test(tensilelite): strengthen Solution derivation contracts
davidd-amd Sep 10, 2026
44c7328
test(tensilelite): strengthen Solution validation contracts
davidd-amd Sep 10, 2026
b4f0b8f
test(tensilelite): add wave-2 coverage characterization
davidd-amd Sep 10, 2026
3ae37b0
test(tensilelite): make coverage artifacts deterministic
davidd-amd Sep 10, 2026
fbcbba9
test(tensilelite): add set-cover emit characterization
davidd-amd Sep 10, 2026
6f2a621
test(tensilelite): add set-cover rejection characterization
davidd-amd Sep 10, 2026
13c70da
fix(tensilelite): guard zero-width MX local reads
davidd-amd Sep 10, 2026
0ccb092
test(tensilelite): add god-file characterization S00-S07
davidd-amd Sep 10, 2026
c9a14dc
test(tensilelite): add god-file characterization S08-S11
davidd-amd Sep 10, 2026
566c3e4
test(tensilelite): reuse codegen characterization results
newling Sep 11, 2026
2e0e03b
test(tensilelite): observe generated instruction changes
newling Sep 11, 2026
1f86844
fix(tensilelite): reject zero-width MX local reads
newling Sep 11, 2026
41e96f0
test(tensilelite): select benchmark problem groups explicitly
newling Sep 11, 2026
eb3c4a3
test(tensilelite): correct coverage rebaseline evidence
newling Sep 11, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,12 @@ def localReadMX(self, writer, kernel, bufferIdx, iui, epsi, tP):
mxUnit: int = kernel["MatrixInstK"] // kernel["ProblemType"][f"MXBlock{mxTc}"]
stridePerRead = instruction.blockWidth * bpr
tilePerRead = stridePerRead // mxUnit
if tilePerRead == 0:
raise RuntimeError(
"localReadMX: unsupported M-major MX-scale local read for tc=%s "
"(blockWidth=%s stridePerRead=%s < mxUnit=%s => tilePerRead=0); "
"UnrollMajorLDS%s==0 with MXBlock%s>0 has no implemented scale layout"
% (tc, instruction.blockWidth, stridePerRead, mxUnit, mxTc, mxTc))
MIWaveGroupShape = [ kernel["MatrixInstM"] * kernel["MatrixInstBM"] * kernel["MIWaveGroup"][0] * kernel["VectorWidthA"], \
kernel["MatrixInstN"] * kernel["MatrixInstBN"] * kernel["MIWaveGroup"][1] * kernel["VectorWidthB"]]
tileSpanInfo = self.getMxsTileSpanInfo(kernel, tc, tile01, writer.states.asmCaps)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,44 @@ def _deriveAndValidateMXScaleLayoutAndTransport(state, asmCaps, archCaps, printR
return True


def _validateMXLocalReadWidth(state, asmCaps, printRejectionReason):
"""Reject MX scale reads that are narrower than one scale block.

The WMMA_V3 in-memory-swizzle path reads one byte per MX scale. For an
M-major LDS layout, one local read spans ``VectorWidth`` scale bytes while an
MFMA input consumes ``MatrixInstK // MXBlock`` scales. A narrower read makes
``LocalReadMFMA.localReadMX`` compute zero tiles per read.
"""
if not asmCaps.get("HasWMMA_V3", False) \
or state["MXScaleFormat"] != "InMemorySwizzle":
return True

for tc in ("A", "B"):
mxBlock = state["ProblemType"][f"MXBlock{tc}"]
if not mxBlock or state[f"UnrollMajorLDS{tc}"]:
continue

mxUnit = state["MatrixInstK"] // mxBlock
vectorWidth = state[f"VectorWidth{tc}"]
if mxUnit <= 0:
reject(
state,
printRejectionReason,
f"M-major MX-scale local read for {tc} requires "
f"MatrixInstK >= MXBlock{tc} ({state['MatrixInstK']} < {mxBlock})")
return False
if vectorWidth < mxUnit:
reject(
state,
printRejectionReason,
f"M-major MX-scale local read for {tc} requires "
f"VectorWidth{tc} >= MatrixInstK // MXBlock{tc} ({mxUnit}), "
f"got {vectorWidth}")
return False

return True


def _disableRuntimeStaggerU(state):
state["StaggerU"] = 0
state["StaggerUMapping"] = 0
Expand Down Expand Up @@ -4026,6 +4064,10 @@ def isAutoLRVW(tc) -> bool:
else:
calLRVW()

if not _validateMXLocalReadWidth(
state, isaInfoMap[isa].asmCaps, printRejectionReason):
return

def calcOptGRVW(lrvw: int, unrollMajorLDS: bool, datatype: DataType) -> int:
# with UnrollMajorLDS, GRVW need to less or equal than LRVW to have conflict free LDS read with padding.
optGRVW = lrvw if unrollMajorLDS else 4 / datatype.numRegisters()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,36 @@ def test_size_matches_n_off(self):
# math.ceil(4/2) = 2 matings → 2 pairs
assert len(pairs) == 2

def test_reroll_when_first_mask_has_too_few_swaps(self, monkeypatch):
"""op() re-draws the mask until it yields at least ``swaps`` set bits.

Covers the ``while mask.sum() < swaps`` re-roll body, whose execution is
otherwise nondeterministic because the mask comes from unseeded
``np.random.random``. Four differing genes give ``swaps == 2``; the first
draw is all-False (0 swaps, forces the re-roll) and the second is all-True
(4 swaps, satisfies the loop).
"""
from Tensile.ductile.core import crossover as crossover_mod

pa = Individual({"DepthU": 0, "SourceSwap": 0, "A": 0, "B": 0}, F=1.0)
pb = Individual({"DepthU": 1, "SourceSwap": 1, "A": 1, "B": 1}, F=2.0)
assert len(pa.diff(pb)) == 4

draws = iter([np.zeros(4), np.ones(4)])
calls = {"n": 0}

def fake_random(n):
calls["n"] += 1
return next(draws)

monkeypatch.setattr(crossover_mod.np.random, "random", fake_random)
cx = Crossover.get("hux", prob=1.0, mode="random")
a, b = cx.op(pa, pb)

assert calls["n"] == 2
assert set(a.names) == set(pa.names)
assert set(b.names) == set(pb.names)


# ---------------------------------------------------------------------------
# SinglePoint crossover (spx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,154 @@ available where this change was authored.
**Decision:** Sort every code object's input paths immediately before linking,
so default and explicitly grouped code objects share one deterministic physical
kernel order even though their inputs originate from different collection types.

## D24 — LibraryIO characterization: add-only mutation-kill snapshot cases
**Decision:** The LibraryIO mutation-hardening slice pins additional *current*
LibraryIO behavior by appending new syrupy cases to three existing goldens
(`test_parse_integration_char.ambr`, `test_serializers_char.ambr`,
`test_writesolutions_char.ambr`); no existing snapshot value is re-recorded.
**Classification:** category (a) intended behavior capture -- new cases pinning
previously-unsnapshotted read/write/parse behavior to raise mutation kill power,
not a change to any pinned behavior. The diffs are insertion-only (+258/-0,
+9/-0, +54/-0) and confined to the LibraryIO node, so no ADR is required (nothing
behavior-changing or known-wrong is pinned); this registry line is the record.
The parse_integration additions begin in the mutation infra base and continue
in this slice.
**Re-run:** goldens byte-identical on two further no-update runs; `-m unit` green.

## D25 — Solution.py mutation kill: pickle-free `.ambr` derivation golden
**Decision:** Kill the `Solution.assignDerivedParameters` mutant giant with a
syrupy `.ambr` full-derived-state golden regenerated from in-tree designed YAML
configs, committing no pickle. See ADR 0003.
**Why:** the giant (~18820 mutants across the `depthU`/`adp` families) is only
observable by asserting the complete derived `_state`; a pickle golden is opaque,
version-coupled, and undiffable, against the suite's add-only diffable-golden
discipline. Unlike the LibraryIO add-only cases in D24, this introduces a new
golden *vehicle* with a non-obvious regeneration mechanism, so it lands with an
ADR (0003), not just this registry line.
**Equivalence evidence:** verified kill-equivalent to the interim pickle corpus
over 8 stratified windows (lines 1567-2857, 684 mutants, 0 per-key exit-code
divergence). Byte-stability confirmed by two further no-update runs.
**Harness fixes (not source):** derivation moved out of collection (a
collection-time try/except was swallowing raising mutants); `_sanitize` now
recurses into any `Mapping` so `ProblemType` is deep-compared, closing 4
`MirrorDimsMetadata` mutants a `str()`-only render missed.
**Regeneration:** on an intentional derivation/config change, rerun with
`--snapshot-update`, confirm byte-stability with two clean runs, and log the
regeneration here.

## D26 — Per-file ratchet baseline refresh after wave-2 (r8 + HUX crossover)
**Decision:** Refresh `coverage-baseline.json` (158 files, tolerance 1.0) from the
combined `coverage-unit` lane on branch `users/davidd-amd/mut-v2-coverage`
(HEAD e69017042cf, coverage.py 7.15.4, branch=True). The prior baseline was the
stale #9123 (`f90130fb37d`) snapshot that predated the whole mutation stack. The
refresh locks in 31 upward floors and accepts 3 sub-tolerance downward moves, each
with an evidence-backed disposition below.
**Why:** the #9123 floors understated real coverage (e.g. Solution.py 70.55,
SubtileGREmit.py 73.39), so a blind ratchet was impossible without either gaming
or losing the mut-stack gains. `coverage_ratchet.py check` was clean (0 regression,
tol 1 pp) before `update`; `tox -e coverage-gate` exits 0 after (TOTAL 78.76% ->
79.11%).
**Upward floors locked (top):** WaitAluInsertion 18.81->69.31, SubtileGREmit
73.39->88.44 (r8, MUTCOV-002), Configuration 91.18->99.25, MAC_F32C 14.58->22.50,
segment_interleave 93.57->100.00, MAC_F64C 16.33->22.73, Solution 70.55->74.58,
TensorDataMover 71.68->74.95, SubtileLREmit 85.49->88.60, BenchmarkSplitter
97.92->100.00.
**Downward dispositions (all sub-tolerance, < 1 pp; ratchet check passed):**
- `KernelHelperNaming.py` 97.10 -> 96.20 (-0.90): source UNCHANGED since the
baseline commit (`git diff f90130fb37d..HEAD` = 0 lines). Arc/branch-accounting
noise, not a coverage loss. Disposition: accept, noise.
- `TensileLogic/Run.py` 89.90 -> 89.15 (-0.75): source UNCHANGED since baseline
(0 lines). Arc noise. Disposition: accept, noise.
- `KernelWriter.py` 78.05 -> 77.72 (-0.33): source CHANGED (+177 lines) by 9
develop-side GPU-feature commits merged after the baseline (#9410 TDM iterate,
#10104 gfx1250 replay-hazard, #10209 segment-conflict, #9851 XFP32, #10298,
#10217, #10210, #10213, staggerU-disable). This is real feature dilution from
GPU paths the CPU-only char lane cannot reach; NOT caused by the mut stack.
Disposition: accept as documented feature dilution; the coverage gap belongs to
those features' owners (author GPU char coverage is out of scope for a
test/config-only change).
**crossover.py:** stays 100% (now deterministic via MUTCOV-003); per MUTCOV-004
scope it is NOT recorded as a new rise.
**Classification:** baseline-maintenance only; no behavior pinned, so no ADR. The
baseline write and this DECISIONS entry land as two separate atomic commits per
the MUTCOV-004 delivery rule. No push; David reviews the baseline diff.

## D27 — Refresh config-driven emit results after develop changes

**ADR:** [`adr/0014-refresh-config-emit-results-after-develop.md`](adr/0014-refresh-config-emit-results-after-develop.md)

**Decision:** Re-record the 75 set-cover emit nodes against current `develop`
and an in-tree `rocisa` build. Seventy-one nodes retain their kernel counts and
emitter return codes. Four reviewed nodes change count, and every retained
kernel still emits with return code `0`; ADR 0014 records those cases and the
upstream cause.

## D28 — Correct the disabled TDMSplit characterization

**ADR:** [`adr/0015-correct-disabled-tdmsplit-test.md`](adr/0015-correct-disabled-tdmsplit-test.md)

**Decision:** Replace the unreachable TDMSplit emission and saved-result checks
with assertions that normal solution derivation returns no kernels and reports
`TDMSplit is currently disabled`. The test no longer claims coverage of emitter
code that product validation prevents it from reaching.

## D29 — Refresh S00-S07 emit results after develop changes

**ADR:** [`adr/0016-refresh-s00-s07-results-after-develop.md`](adr/0016-refresh-s00-s07-results-after-develop.md)

**Decision:** Re-record only the 20 failing S00-S07 saved-result nodes with an
in-tree `rocisa` build. Every node retains its kernel count and emitter return
codes; only the content-derived basenames change.

## D30 — Refresh S08-S11 emit results after develop changes

**ADR:** [`adr/0017-refresh-s08-s11-results-after-develop.md`](adr/0017-refresh-s08-s11-results-after-develop.md)

**Decision:** Re-record only the 11 failing S08-S11 saved-result nodes with an
in-tree `rocisa` build. Every node retains its kernel count and emitter return
codes; only the content-derived basenames change.

## D31 — Rebaseline coverage after the develop rebase

**ADR:** [`adr/0018-rebaseline-coverage-after-develop.md`](adr/0018-rebaseline-coverage-after-develop.md)

**Decision:** Regenerate the per-file baseline from the green post-rebase unit
run. The update raises 16 floors, adds 14 current files, removes two entries for
files deleted by develop, and explicitly lowers the nine reproducibly stale
floors listed in ADR 0018. The tolerance remains 1 percentage point. Superseded
by D35, which corrects the reduction count and file classification.

## D32 — Config-driven saved results include emitted assembly

**ADR:** [`adr/0019-pin-config-driven-assembly.md`](adr/0019-pin-config-driven-assembly.md)

**Decision:** Record a SHA-256 digest of the emitted opcode set next to each
config-driven kernel's name and return code. This makes a change in instruction
kinds observable while ignoring known register, label, count, and order
variation.

## D33 — Reject zero-width MX local reads before code generation

**ADR:** [`adr/0020-reject-zero-width-mx-local-reads.md`](adr/0020-reject-zero-width-mx-local-reads.md)

**Decision:** Reject a WMMA_V3 in-memory-swizzled MX solution during derivation
when an M-major local read is narrower than one scale block. Remove three tests
that counted code reached only before the previous code-generation exception.

## D34 — Select config problem groups explicitly

**ADR:** [`adr/0021-select-config-problem-groups.md`](adr/0021-select-config-problem-groups.md)

**Decision:** Include a `BenchmarkProblems` index in every set-cover case and
pass it through the config-driven harness. This records which problem group is
measured when a shared YAML contains more than one group.

## D35 — Correct and refresh the post-mutation coverage baseline

**ADR:** [`adr/0022-correct-coverage-rebaseline.md`](adr/0022-correct-coverage-rebaseline.md)

**Decision:** Correct ADR 0018's accounting from nine to ten original floor
reductions, document the omitted `Configuration.py` and `Solution.py` changes,
and refresh the baseline after removing invalid pre-exception coverage. The
refresh lowers three reviewed floors, raises ten, and adds two current files.
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,32 @@ def test_parse_logic_list_no_perfmetric(snapshot):
assert _norm(L.parseLibraryLogicList(d, "src.yaml")) == snapshot


def test_parse_logic_list_too_short():
def test_parse_logic_list_range_logic_comes_from_index_eight():
d = _logic_list()
d[8] = {"rules": [[1, 2], [3, 4]]}
d[9] = "reserved"
assert L.parseLibraryLogicList(d, "src.yaml")["RangeLogic"] == d[8]


def test_parse_logic_list_too_short(capsys):
with pytest.raises(SystemExit):
L.parseLibraryLogicList([{"MinimumRequiredVersion": "5.0.0"}], "src.yaml")
assert capsys.readouterr().out == (
"Tensile::FATAL: Library logic file src.yaml is missing required "
"fields (len = 1 < 9)\n"
)


def test_parse_logic_list_missing_type():
def test_parse_logic_list_missing_type(capsys):
# data[11] absent/falsy -> missing matching property -> printExit.
d = _logic_list()
d[11] = None
with pytest.raises(SystemExit):
L.parseLibraryLogicList(d, "src.yaml")
assert capsys.readouterr().out == (
"Tensile::FATAL: Library logic file src.yaml is missing required "
"field matching property.\n"
)


# ===========================================================================
Expand Down Expand Up @@ -170,6 +185,17 @@ def test_raw_library_logic_dict_format(snapshot):
assert _norm(L.rawLibraryLogic(data)) == snapshot


def test_raw_library_logic_preserves_range_logic():
data = [
"5.0.0", "sched", "gfx942", ["Device 0049"],
{"OperationType": "GEMM"}, [{"SolutionIndex": 0}],
[0], [["k", "v"]], {"RangeRules": [[1, 2], [3, 4]]},
]
result = L.rawLibraryLogic(data)
assert result[8] == data[8]
assert result[9] == []


# ===========================================================================
# createLibraryLogic — synthetic problemType/solutions, getCUCount controlled
# ===========================================================================
Expand Down Expand Up @@ -261,6 +287,17 @@ def test_create_library_logic_tile_selection(monkeypatch, snapshot):
assert _norm(data) == snapshot


def test_create_library_logic_empty_tile_indices_are_preserved(monkeypatch):
monkeypatch.setattr(L, "getCUCount", lambda: 304)
logic_tuple = _logic_tuple({(1, 1, 1): [0, 1.0]}, tile=True)
logic_tuple[6] = []
data = L.createLibraryLogic(
"aquavanjaram", "gfx90a", ["Device 0049"], "Matching", logic_tuple
)
assert data["TileSelectionIndices"] == {"TileSelectionIndices": []}
assert len(data["Solutions"]) == 2


def test_create_library_logic_with_metadata(monkeypatch, snapshot):
# ProblemType.state carrying the optional DataTypeMetadata / MXSA / MXSB
# keys -> the three guarded conversion branches run.
Expand Down Expand Up @@ -314,6 +351,16 @@ class _Res:
assert L.getCUCount() == 110


def test_get_cu_count_uses_last_rocminfo_device(monkeypatch):
monkeypatch.delenv("CU", raising=False)

class _Res:
stdout = b"Compute Unit: 110\nCompute Unit: 228\n"

monkeypatch.setattr(L.subprocess, "run", lambda *a, **k: _Res())
assert L.getCUCount() == 228


def test_get_cu_count_rocminfo_no_match(monkeypatch):
# rocminfo output without a "Compute Unit:" line -> regex misses ->
# CU stays None -> printExit (covers the 696->701 no-match branch arm).
Expand Down
Loading
Loading