[Feature] kernelentry: make "the simulator supports this kernel" a computed ledger - #213
[Feature] kernelentry: make "the simulator supports this kernel" a computed ledger#213WarningRan wants to merge 10 commits into
Conversation
`dram_bytes` answers how much traffic a kernel moved; nothing answered which tensor moved it. That makes a claim about composition — "this weight tensor's traffic is negligible" — uncheckable, because it is a statement about one term of a total rather than about the total. `_estimate` already computes each op's bytes and FLOPs and `record` already receives them, but only the per-category sums were kept. Retain them per operation instead, alongside the memory the operation addressed, and add `LatencyReport.traffic_by_target()` to aggregate on it. The target is the enclosing `MemRef.base_ptr` — an element index, so it compares directly against the pointer `execute_function` binds to an argument, which `KTIRInterpreter.arg_ptrs` now retains. Bytes whose origin is not resolvable are kept under a `None` key rather than dropped: a breakdown that silently omits a row adds up to less than the total while looking complete. A distributed memory view appears as one key per partition, since `distributed_load` charges each surviving partition separately; folding those into one row per argument needs each argument's element extent, which the report does not know. `tests/test_latency.py` covers both halves of the new method. The attribution is hand-counted off the IR for `matmul_small` — one row per argument at the pointer it was bound to, four times as many bytes against B as against A, and the rows summing to `dram_bytes` — because a total of 45,056 says nothing about the split and a sentence calling one of the two negligible survives the total but not the breakdown. Without the trace the method raises rather than returning an empty mapping, and it distinguishes the two ways there is nothing to report: a report covering no core at all, and one covering cores that were never traced. Returning an empty mapping for either would make a missing constructor argument look like a finding about the kernel. The same file gains a chip-wide hand count of bytes and FLOPs for `matmul_small`, the companion to the existing single-core count for the simplest kernel, at exact equality rather than a tolerance — both sides are integer counts, and a mis-charged operation is exactly the small disagreement a tolerance hides. The stick-rule arithmetic behind the two `matmul_small` hand counts — this one and the attribution above — is written once and shared, so the two cannot drift into disagreeing about the same kernel. No cost formula changes. The trace only carries figures it was already given. Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
The three parts of kernelentry that stand on their own, ahead of the engine that
reads them: what a kernel declares, how its arguments are built, and the one check
that is asked of the repository rather than of any kernel.
A declaration is a row — the kernel's file, the parameters the gate runs it at, its
arguments, a reference, and any excuse. Six claim states, and two of the
distinctions are load-bearing. `undetermined` is separate from `closed` because a
check the engine could not evaluate must not read as one that passed — and without
the state such a claim gets omitted instead, which is indistinguishable from clean
in any summary. `deferred` is separate from `waived` because only one of them comes
back: a deferral carries an issue reference and reads as a known gap rather than a
failure, following `tests/test_spec_gaps.py`. Either excuse reports itself as
unnecessary once its check starts passing, which is what fails the build until the
declaration is updated — the xfail marker cannot do that on its own, since it is
applied from the claim's current state and is simply absent once the gap closes.
That is what lets a kernel arrive over two pull requests without the first
pretending the cost leg is done. Both excuses are validated where they are written
rather than where they are read: a deferral naming no issue, and a tolerance naming
an argument the kernel does not have, are rejected at declaration.
An argument is a spec resolved against the row's parameters — `normal(("M", "K"))`,
`zeros(("M", "N"))`, a bare string forwarding a parameter — so declaring a kernel
writes a row and not a file, and a callable `(params, rng)` remains for the
arguments no spec covers. Specs are per argument rather than per kernel, which is
what lets a kernel declare one tensor the long way and leave the rest as rows.
Every draw is seeded from the argument's own name: one generator drawn twice gives
the second tensor the first one's values, and a kernel that swapped its operands
would then still agree with its reference.
The pricing check is repository-wide rather than a per-kernel claim, which a
probe-only prototype over six real artifacts settled. Asked per kernel it produced
66 of that prototype's 74 open claims, about fifteen of them the same structural
ops in every kernel — `arith.constant`, `scf.*`, `construct_memory_view`,
`return` — which really are free; per kernel it is a chore each contributor waives
their way through, and a waiver mapping that is mostly noise stops carrying
information. Asked once per repository it is `pricing.py`: every op the registry
prices `zero` — 46 of 106 today — has to appear in exactly one of two mappings,
each entry carrying a written reason. `ZERO_COST_OPS` is free by decision, 21 ops;
`UNJUDGED_ZERO_OPS` is priced zero with nobody having decided that, 25 ops, each
reason naming the issue that would settle it, because an open question with no
issue behind it is how such a list becomes permanent. An op in neither, or in both,
is a finding. So registering an op without naming a category no longer passes
silently — and it does not become priced either, which is the point: the gate asks
for a decision, and recording "not yet judged" against an issue is a legitimate
answer.
What the audit cannot see is a category that is simply wrong. An integer compare
billed to the float pipe is priced, so a comparison against `zero` passes it, and
that defect survived in this repository for as long as it existed. Only a reader
who knows the op's semantics catches that one.
`conformance.py` records which kernels under `examples/` the MLIR frontend rejects,
and why. That has to be written down rather than measured at render time: the check
needs an optional dependency, so on a machine without the MLIR bindings the claim
reads `skip`, and a report carrying whatever this machine saw would be a document
about the machine. It is a record and not a verdict — it does not say whether the
file or the parser should change. Same shape and same reason as
`tests/mlir_frontend/test_registry_consistency.py::FRONTEND_UNSUPPORTED`.
Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
"Fully supported" was an adjective, and an adjective cannot be checked. Three kernels have now gone through this repository, each as a kernel plus a notebook section, and what they delivered under `tests/` varies sevenfold across the commits that carry them: 471, 363, 220, 84 and 66 lines. The 84 asserts an arithmetic-intensity formula, per-core cycle structure and three input guards, and compares no output value. Two simulator gaps were found by walking into them mid-change rather than by asking first. Closes torch-spyre#209. kernelentry makes support a ledger: a set of claims derived from the kernel itself, each closed or open with the file that would close it named. The set is not a checklist — every distinct op contributes a claim, every output tensor contributes two — so it grows with the kernel rather than with this module. python -m ktir_cpu.kernelentry probe examples/latency/my_kernel.mlir python -m ktir_cpu.kernelentry adopt my_kernel python -m ktir_cpu.kernelentry probe --all --write-report python -m ktir_cpu.kernelentry verify --all `probe` takes the bare `.mlir` and writes nothing, so the first question — what does the simulator not support about this kernel — can be asked before there is a declaration to ask it with. `adopt` writes only the files it owns and never edits the interpreter: when a handler or a repriced op is needed it prints the change and the file, because a tool that quietly changed what the simulator charges would be changing the answer it was asked for. `verify` shares one engine with the gate, so the local loop and CI cannot disagree. Support is five questions asked in order — read by the regex parser, read by the MLIR frontend, runs, output right, cost pinned — and a kernel with no declaration answers a prefix of them. That ordering is why the generated report has two tables rather than one with holes: an unasked question has no cell, not an empty one. Output tensors are identified from the trace rather than by walking SSA names back from a store. The walk does not survive `construct_distributed_memory_view`, and when it fails it fails silently: the prototype produced a kernel with no output claims at all and a summary line reading 60/61 closed. Deriving the set from the trace also means a declaration can name an output the trace does not, so a reference entry no store wrote is reported `undetermined` at the id its comparison would have had. Left unasked it is the same defect one step over: a comparison that vanishes rather than fails. There is deliberately no per-op frontend-reachability claim. `tests/mlir_frontend/test_registry_consistency.py` already asserts that every executor op is frontend-installed or allow-listed, and an op appearing in a kernel is necessarily registered, so an unreachable op already breaks the build repository-wide; `parse.frontend` catches it for one kernel specifically. A third check would restate them and would need the allow-list moved out of that test. `cost.derivation` commits a generated attribution of the kernel's cost, so a change arrives as a diff naming the term that moved and a reviewer reads a breakdown instead of a number. It cannot catch the cost model being wrong, since it is produced by the same code as the measurement, and it says so in its own footer. That question is not asked per kernel: there is one cost model behind all thirty-three kernels, and `tests/test_latency.py` already asks it by mechanism, with hand-counted bytes, FLOPs and cycles per latency category and across the hardware parameters that scale them. A per-declaration hand count would have each kernel re-answer one question about one model, so support is five ordered questions and not six. Reading `examples/` for discovery, and the frontend allow-list out of the test that enforces it, makes this package a development tool rather than part of the library, so `pyproject.toml` excludes it from the wheel. Packaged it would resolve to a `site-packages` directory carrying neither path, and fail where it read them rather than at import. Editable installs, which is how the repository is worked in, are unaffected. `docs/kernelentry.md` is the contributor entry point — the five questions, the six states, how to declare a kernel, how to excuse a claim, and which kernels cannot be declared at all — and `CONTRIBUTING.md` links it from the pull-request section. Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
The ledger the previous commit adds asks the MLIR frontend whether it accepts each
kernel, and these two were the only ones it refused that anything here declares.
Both write `inter_tile_produce` / `inter_tile_reduce` as
: T -> !ktdp.tile_future<T, groups = S>
which the regex parser reads and the built dialect does not print, so the frontend
stops at the first of the two ops with `expected '->'`. The dialect's own custom
assembly is
-> <(T), groups = S> (produce)
: <(T), groups = S> -> R (reduce)
and the regex parser reads that form as well, which settles which of the two
spellings is authoritative: both parsers accept the second, only one accepts the
first.
Two lines per file. The authors' line breaks are left alone, so the diff is the
type syntax and nothing besides it, and nothing about either kernel moves: both
parsers accept the result, and the cost derivations are identical to the digit,
because the spelling of a type is not an input to the cost model.
This precedes the generated reports so that no committed document records an
acceptance the tree does not have at that commit.
The three `ring_reduce*` kernels are deliberately left in the old form. Rewriting
their spelling does not make them parse: all three reduce `tensor<1x128xf16>` to
`tensor<128xf16>`, and the dialect verifies that a reduce's result matches the
future's partial type, so the error becomes `result types must match future partial
types` instead of disappearing. The reduce here takes its result type as a reshape
target (`_result_shape` -> `attach_reshape` in `ktir_cpu/dialects/ktdp_ops.py`), so
closing that gap means deciding whether it should reshape at all — a decision about
the op rather than about how those three files are written down. They stay recorded
as rejected, with that distinction in the reason and in gap row 2a.
Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
Every kernel under `examples/` that can be driven through the `tensors=` seam is
declared, and every claim the eighteen raise is closed. Declaring one is a row in
the `ENTRIES` table in `examples/entries.py` — no new file, nothing to register, no
test to write. The blocker looked larger than it was: seventeen of these kernels
already had a reference inside `tests/`, computed in f32 and then cast back to f16
before comparison, and the cast is what made it a reference to the kernel's own
rounding rather than an independent one. Dropping it is a one-line change per
kernel, at the tolerances the ledger already used. Three needed more than a
transcription:
- `ffn_swiglu_4core` overflows f16 in `exp` at unit-variance weights and writes
an all-zero output, which no cost report would flag. Its inputs are scaled by
1/sqrt(fan-in) — standard initialisation, not a number chosen to make a check
pass — and `out.*.nontrivial` is what noticed.
- `layernorm_fwd_ktir` writes three tensors and the end-to-end test checks two.
`Rstd` now has a reference, so the reciprocal standard deviation of a fold over
8192 f16 values is compared against something for the first time.
- `paged_attention` is referenced at all thirty-two grid positions rather than
the first, and with the softmax taken flat over the masked row rather than
folded tile by tile. Both matter: a mask indexed by query position is exactly
what can be right at `pid0 = 0` and wrong everywhere else, and the tiled fold
is the kernel's own algorithm, so a reference written that way checks the
arithmetic and not the decomposition.
The fifteen that remain undeclared are not a backlog. Eight `examples/rfc/*` files
take no tensor arguments — their tensors are memrefs at absolute HBM bases, so
there is nothing to bind, and the two that take anything take one scalar size.
Five take raw HBM element indices with no shape attached — the three ring-reduce
kernels and the two `rmsnorm_4core_*` — and the tests that run them seed memory by
replacing `_prepare_execution`, which a declaration cannot express; that is a
property of the argument convention rather than of those files, and it is the live
one, since the two rmsnorm kernels are the most recent to arrive and their tests
seed memory the same way. `softmax_wide.mlir` overflows LX on purpose and its test
asserts the exception, so `exec.runs` failing is the kernel behaving.
`nested_yield.ktir` is rejected by the frontend for a `construct_memory_view` with
no `coordinate_set`, and it is a reproducer written down to the one op under test,
so being minimal rather than dialect-valid is what it is for.
No declaration defers or waives a claim. That is worth stating rather than
assuming, because the two that would have — the cross-core kernels of the previous
commit — are the reason `deferred` exists in the engine at all, and the state ends
up exercised only by the gate. The gap this branch found and did not close is the
one it cannot: the three `ring_reduce*` kernels are on the page, in the record of
what the frontend refuses, with the decision their author has to make written
beside them.
The gate's cost is shape, not entry count: probing all eighteen takes about 16 s,
and timed one kernel at a time, five kernels whose shapes are baked into their IR
are 94% of it. They are here anyway, because a cost model checked only at reduced
size is checked where the padding, the tail core and the page table are not real
yet.
Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
Generated and committed on the same discipline as a lock file: regenerating them is one command, and the gate compares them verbatim, so adding a kernel without regenerating them is a red build rather than a document that quietly goes stale. `docs/kernel_support.md` and `docs/supported_ops.md` are the same repository seen per kernel and per op; `docs/kernel_cost.md` is the committed cost attribution the `cost.derivation` claim compares against. Between them they carry figures nothing here stated before: 490 op occurrences across thirty-three kernels with a handler for every one, 106 handlers of which 102 are frontend-reachable and four are by design not, and 52 of those 106 exercised by an example. Four kernels are not accepted by the MLIR frontend — the three `ring_reduce*` for the typing gap recorded earlier in this branch, and `nested_yield.ktir` for a `construct_memory_view` with no `coordinate_set` — which nothing said before either: the frontend suite covers eleven kernels by inheriting regex test classes and none of these four is among them, so this is a coverage hole rather than a break. The op document's cost column prints the pricing decision rather than the registry's default, so an op priced zero with nobody having decided that reads differently from one that is free. `docs/kernel_cost.md` is worth reading rather than skimming. 72.7% of `matmul_small`'s HBM traffic is the B operand, at a traffic ratio of 3.667 over what its tensors logically require; `indexed_add` reports 0.02320 for the same figure, which is not an error — it reads two of 128 slices, and clamping the ratio at 1 would erase exactly the fact worth seeing. The spread across the eighteen is 0.02320 to 32. Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
The gate for everything above: one parameterized case per claim per declared
kernel, over the same engine `verify` runs, so the local loop and CI cannot come to
different conclusions about the same tree. A claim's state decides its marker, so a
deferral is an `xfail(strict=True)` and closing the gap it names fails the build
until the declaration stops claiming it.
A gate can also be green because nothing it asks is hard, and that is what the
second suite is for. It removes a reference, narrows one to a slice of the output,
swaps in a reference computed in f16, adds a reference key no store wrote, drops a
declared tolerance, staleness-checks a waiver and a deferral by closing the gap
each excuses — and asserts the claim turns each time. A claim that cannot be made
to fail there is decoration, and the suite is where that gets established rather
than assumed.
Three more things are asserted rather than trusted:
- The generated documents are functions of the repository and not of the host,
checked by rendering them twice with the MLIR bindings stubbed out. Without
that they would differ between CI and a contributor's laptop, and each would
call the other's stale forever.
- The support report covers every example `tests/conftest.py` already drives, so
a kernel cannot be added to the test parameters and stay invisible to the
ledger. The same test pins the one figure the renderer cannot compute — how
many of those listings pass no arguments — because the package cannot import
the test suite, and a hand-written count in a generated document is the rot
this whole change is against.
- The pricing audit's blind spot is written down as a test: an op billed to the
wrong pipe is still priced, so no comparison against `zero` can catch it. Left
unstated it is a gap someone rediscovers; stated, it is the reason the
follow-up asks for a semantic audit.
`tests/mlir_frontend/test_kernelentry_adapt.py` verifies `conformance.py` against
the real frontend in both directions, so an unrecorded rejection and a rejection
since fixed each fail. It also asserts that `parse.frontend` comes back as a real
answer rather than `skip` where the bindings are installed, because a leg that
silently skips is a leg that reports green without being asked.
Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
Generating an op matrix makes two hand-maintained inventories checkable for the first time, and both were wrong in the same direction — reporting as missing what has been implemented for months. `docs/gap_analysis.md` rows 13–19 (`arith.cmpf`, `negf`, `absf`, `minf`, `minnumf`, the float casts, the signed and bitwise int ops) and rows 28–29 (`tensor.extract_slice`, `tensor.insert_slice`) were ❌ or 🟡 with handlers in the registry; `cmpf` and `negf` are used by an example. They are now ✅ with the handler cited, and the two summary lists at the end agree with them. The Arith and Tensor preambles no longer carry an inventory at all — they point at the generated document, because a list copied into prose is what let those rows sit wrong, and re-copying it correctly would only restart the clock. The coverage-backlog paragraph named three RFC-mentioned ops as absent; `memref.subview` is the only one still absent, and it now names just that one. README's Supported Subset had the same problem from the other side: it described rectangular coordinate sets only, and omitted `base_map`, `access_tile_order`, distributed and indirect views, and cross-core reduction. Its Not-Supported list named `ktdp.transfer` / `ktdp.reduce`, which no longer exist, and `tensor.insert_slice`, which does; and it called `coordinate_set` unenforced, where what is actually unchecked is overlap between a distributed view's partitions — `find_partition` returns the first match, which RFC 0682 §3.3 leaves unspecified. Both lists are rewritten and now link the two generated documents rather than restating them. New gap row 36a, found by the same matrix: `ktdp.region_terminator` has a frontend adapter and no execution handler. The MLIR bindings' region walk is documented in `mlir_frontend/parser.py` as emitting an implicit terminator that never appears in text IR; an adapter is installed so the walk survives, `adapt_block` keeps it in the region's op list, and `_execute_op` would raise `Unknown operation` for it there. No kernel under `examples/` reaches it, and the reason is not the one it looks like. Three inter-tile kernels do get through the frontend — `rmsnorm_4core_2x2.mlir` and the two rewritten earlier in this branch — and walking every op the frontend produces for each, into the `inter_tile_produce` and `inter_tile_reduce` regions, finds `ktdp.yield_partial` and `ktdp.yield_reduced` terminating them and no `ktdp.region_terminator` in any of the three. So the row records an asymmetry that is real and a construct whose trigger is not established, rather than a kernel that would abort. Row 2a is corrected by the same leg. It described the four-op design's upstream status rather than this repository, and the status it described is no longer the case; it now states what is true of the code here — `ktdp.inter_tile_reduce` takes its result type as a reshape target, the dialect verifies the result against the future's partial type instead, and the three `ring_reduce*` kernels are the only non-identity reduce reshape and the only inter-tile kernels the frontend refuses. Rows 2b-2d are left alone: their implementation status is unmeasured here. §I and §J are left alone; they are dated 2026-05-30 and are a different kind of claim. Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
|
Three notes to save time on a diff this size:
The gate is |
|
@WarningRan i think need to clarify the purpose of this PR, most of the kernels examples in ktir-cpu, are meant just for testing and are seperate from actual kernels that we want to support. The kernels that are to be supported are versioned in the https://github.com/torch-spyre/spyre-kernels/ |
|
@fabianlim Where I stand: the kernel comes from elsewhere: we do not produce kernels. So I agree with your line, and this PR stands on it: Good point on the last one. |
`probe <path>` refused any `.mlir` not under `examples/`, telling the caller to move it there first. That inverts what the tool is for. The kernels this interpreter is asked to support are versioned elsewhere; `examples/` holds fixtures, and the two questions a cold probe answers — whether every op in the file has an execution handler, and whether both parse paths accept it — are about the simulator rather than about where the file sits. A cold probe writes nothing, so a path outside the tree leaves nothing behind to go stale. The entry keeps the relative form when the file is under `examples/` and the absolute path when it is not, so a cold probe and a declared entry name the same kernel the same way. Declarations are deliberately not relaxed with it: a row in the committed report names a repository-relative path, and a row pointing at one person's own disk is a row nobody else can regenerate. Signed-off-by: WarningRan <zhuoran.liu@ibm.com>
|
Sorry, missed the comments, @lasch . This is the commit I said was coming in the reply above, so it landed here rather than separately: 40 lines, confined to the cold-read path, no change to how a declared kernel is resolved. It dismissed your approval, which I did not intend. Happy to pull it out into its own PR if you'd still rather have it separate — say so and I'll reset it back to the tree you approved. |
I'm not going to reject a PR because it was made more complete 😉 |
Closes #209.
Summary
kernelentryturns "the simulator supports this kernel" from an adjective into a ledger: claims derived from the kernel itself, each closed or open with the file that would close it named. Every distinct op contributes a claim and every output tensor two, so the set grows with the kernel rather than with the tool.Point it at an
.mlirbefore any paperwork exists:What needs to run the kernel says why it could not, instead of being skipped. Declaring it — one row in
examples/entries.py— answers all five questions.probewrites nothing,adoptwrites only the files it owns and prints rather than applies any interpreter change, andverifyshares one engine withtests/test_kernelentry.py.Eighteen of the thirty-three kernels under
examples/are declared, and no claim is open, deferred or waived anywhere in the repository. The other fifteen cannot be driven through thetensors=seam at all; the report names each with both reading answers, anddocs/kernelentry.mdgroups them by which of four reasons applies.What the tool found
Per kernel and per op it is all in
docs/kernel_support.mdanddocs/supported_ops.md. The headlines:mainis green because the adapt suite covers eleven and none of those six — a coverage hole, not a break. Two are fixed here (the dialect prints the op another way: two lines of type syntax, cost figures identical to the digit) and four recorded, because the threering_reduce*want a reduce that reshapes, which is a decision about the op rather than a rewrite (gap row 2a), andnested_yield.ktiris a separate reproducer.ktdp.region_terminatorhas a frontend adapter and no execution handler (row 36a). No kernel underexamples/reaches it, so the asymmetry is real but the construct's trigger is not established.docs/gap_analysis.mdrows 13–19 and 28, and README's Supported Subset.memref.subviewis the one op they claimed that is genuinely missing.tests/, computed in f32 and then cast back to f16 before comparing — which discards exactly the bits the comparison exists to look at. Three were also narrower than their kernel:layernorm_fwd_ktirwrites three tensors and its test checks two, andpaged_attention's reference is the kernel's own algorithm at one of thirty-two grid positions.ffn_swiglu_4coreat unit-variance weights saturates half itsgatetensor, so its row draws weights at fan-in scale to keep the comparison about whether the four shards folded. Single-coreffn_swiglukeeps unit variance, so the saturation stays somewhere in the repository.out.*.nontrivialwas justified by an f16 overflow in the RMSNorm notebook path, wherezero_rowsis 0 at all three of that notebook's configurations. It is kept on other grounds — see the second limitation.Commits
A large change: 6488 lines added, of which 1149 are generated documents and 1054 are tests. Read in commit order; no commit rewrites an earlier one and each leaves the suite green.
docs/kernelentry.md.What is NOT changing
No cost formula and no hardware constant — the trace carries figures
_estimatealready produced.tests/conftest.pyandEXAMPLE_PARAMSare untouched, and agate_paramsis not a second copy of one: only one of the eighteen coincides with itsexecute_kwargs. No test is deleted or weakened, andnotebooks/is untouched.FRONTEND_UNSUPPORTEDstays where it is, which is why there is deliberately no per-op frontend claim. No newktdpop, attribute or semantics — the tool reports where a file and the dialect disagree without adjudicating it.Test plan
parse.frontendwas checked only because the bindings are installed here.tests/mlir_frontend/skips at module level withoutmlir_ktdp, and green from a run that skipped it is not green, so the frontend leg has a suite that fails if the claim comes backskipwhere the bindings exist. The rejections and acceptances above are measured, not asserted.TestClaimsDetectViolationsis the part worth reading. It removes a reference, swaps in an f16 one, adds a reference key no store wrote, staleness-checks an excuse and closes a deferral's gap, asserting the claim turns each time. A claim that cannot be made to fail there is decoration.parse.frontendclaims — two xfails and two skips became passes.Follow-ups (not in this PR)
@register()defaultslatency_categoryto"zero", so "free" and "unpriced" are one state, and an op priced in the wrong category is still priced — no per-kernel claim substitutes.COMPUTE_INThas no assertion anywhere.ktdp.region_terminatorneeds an execution handler (row 36a), and whetherktdp.inter_tile_reduceshould reshape at all (row 2a, corrected here to state the divergence rather than an upstream status).Known limitations
cost.derivationcannot contradict the cost model. The breakdown and the totals come from the same code, so a mis-charged op moves both together. What it catches is prose disagreeing with the attribution — "the weight operand's traffic is negligible" against a row reading 72.7%. Whether the model charges correctly istests/test_latency.py's question, asked by mechanism rather than per kernel.atol=2e-1exceeds the tensor's entire dynamic range —layernorm_fwd_ktir'sMeanandsoftmax_fwd_ktir's output — so those comparisons would also pass against an all-zero output, andout.*.nontrivialis the only claim behind them. Eleven of the twenty need noatolat all; the pair is sized bymatmul_fwd_ktir, which needs 0.103 of it across 524288 f16 values.ffn_swiglu's chained f16 matmuls accumulate through peaks far above the values they end on, so an element that cancels escapes a relative tolerance;atolcomes from the tensor's largest peak rather than from the element that cancels at this seed, because a shape change moves which element that is. The arithmetic is written beside the row, the claim reports the pair even when it closes, and a test asserts that removing the override reopens the claim.nested_yield.ktiramong them is a reproducer whose dialect validity is somebody else's open question.