forked from ccf-ai-infra/TileOPs-Metax
41 Commits
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
c2e01ac8d4 |
[Fix][Manifest] adopt documented shape/workload format in new elementwise families
- Convert signature shape values from YAML lists to string form ("[M, N]")
in elementwise_generative.yaml and elementwise_fused_gated.yaml to match
docs/design/manifest.md R8 + the convolution.yaml precedent so the
validator can bind shape symbols.
- Add per-input workload shape keys (device_carrier_shape, x_shape) to
every workload row to satisfy the workload schema documented in
docs/design/manifest.md and used across normalization.yaml etc.
- Restore exception class/message in check_c4_forward_signature_parity's
warning when inspect.signature(forward) raises.
Co-Authored-By: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com>
|
|
|
|
855adcf333 |
[Maintain][Manifest] align L1 generative detection with C4 positional filter
The L1 carve-out at check_l1_pyfile_signature used _get_forward_params,
which counts KEYWORD_ONLY params. check_c4_forward_signature_parity
filters to POSITIONAL_ONLY / POSITIONAL_OR_KEYWORD before applying its
ref_api == none + zero-positional-args carve-out. A forward(self, *,
dtype=None) class therefore diverged: L1 emitted a [signature] error
while C4 returned []. Extract _forward_positional_params and use it from
both sites so the two carve-outs stay in lockstep.
Co-Authored-By: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com>
|
|
|
|
ff32723d16 |
[Maintain][Manifest] add generative-op carve-out + restore alibi/sinusoidal
L1 and C4 forward()-arity checks now skip when ``ref_api: "none"`` and
``forward()`` takes zero positional args. The five elementwise ops
without manifest entries -- AlibiFwdOp, SinusoidalFwdOp, plus the three
fused-gated entries already present -- are all covered by the manifest;
the two generative ops carry a single ``device_carrier`` scalar input to
satisfy ``test_every_signature_has_inputs_and_outputs`` while the new
carve-out unblocks the forward()-order check.
Co-Authored-By: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com>
|
|
|
|
0451c058a9
|
[Chore][Release] merge testbed into main: op-family alignment + new ops (#1380)
## Summary Merge 27 commits accumulated on `testbed` into `main`. Covers manifest-spec alignment for the elementwise (unary/binary/multi-input), reduction, and normalization families; new ops (LerpTensorFwd, Reciprocal, fused tanh-GELU, InstanceNormFwdOpNoAffine, GroupNorm/InstanceNorm no-affine variants); shared dispatch/refactor cleanup; and supporting manifest DSL (`promote_int_to_float`), tests, and process docs. ## Highlights - **Family alignment to manifest spec**: elementwise_unary_activation (#1211), elementwise_binary (#1222), elementwise_multi_input (#1229), reduction (#1235), normalization (#1240). - **New ops / variants**: LerpTensorFwd (#1264), Reciprocal + `promote_int_to_float` DSL (#1252), fused tanh-GELU (#1262), GroupNorm/InstanceNorm no-affine (#1274, #1284), InstanceNormFwdOpNoAffine running-stats (#1375). - **Refactors**: activation shared bases (#1230), dispatch_kernel routing (#1260, #1368), normalization hotfix cleanup (#1256), elementwise_binary torch-fallback removal (#1248). - **Tests / docs**: manifest-driven L1 parity for elementwise_binary (#1370), drop manifest-mirror tests (#1374), FLIP_STATUS carve-out (#1258), spec-only -> implemented promotion rule (#1271). ## Test plan - [ ] CI green on the PR branch - [ ] \`make test\` passes locally - [ ] Manifest validator and strict-parity gates pass --------- Co-authored-by: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com> |
|
|
|
a127738c3e
|
[Feat][Manifest] strict parity CI for status: implemented ops (#1292)
Refs #1291 · backlog + flip-to-blocking in #1372 Adds C1–C7 strict parity gates to `scripts/validate_manifest.py` for `status: implemented` ops; `spec-only` skip. Removes the `parity_opt_out` escape hatch. | Check | Catches | | --- | --- | | C1 / C2 | `_infer_output_shapes` / `_validate_dtypes` disagrees with manifest | | C3 | ctor param defaults + kw-only flag mismatch | | C4 | forward positional names mismatch | | C5 | Slot S12+S13: `__init__` lacks `kernel_map` kwarg, or body never calls `self.dispatch_kernel(...)` / `super().__init__(...)` (static inspect + AST; no construction) | | C6 / C7 | `_validate_dtypes` / `eval_roofline` is the base stub | Default **advisory** — failures → warnings, exit 0. `--strict` / `MANIFEST_STRICT_BLOCKING=1` flips to blocking. `--levels` gates: C3/C4/C5 ↦ `signature`, C6 ↦ `dtype`, C7 ↦ `bench`. ## Test plan - [x] pre-commit - [x] `pytest tests/test_validate_manifest.py -q` — 205 passed (+19 nodes) - [x] `python scripts/validate_manifest.py` — exit 0 (advisory) - [x] `python scripts/validate_manifest.py --strict` — exit 1 (83 errors: 22 ctor + 46 + 15 stub; tracked in #1372) --------- Co-authored-by: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com> |
|
|
|
2de052e19a
|
[Refactor][Hygiene] strip development-process metadata from shipped source (#1290)
## Summary
Apply `.claude/rules/code-style.md` "no development-process metadata in
shipped source" across `tileops/` `tests/` `benchmarks/` `scripts/`.
Rewrite `pytest.skip`/`xfail` reasons, docstrings, comments to describe
invariants instead of referencing issue/PR numbers and AC labels.
Prose-only — no behavior change.
## Scope (24 files)
- 16 `pytest.skip`/`xfail` reason rewrites (tilelang 0.1.9 batch, rope,
attention, recurrent linear-attention) + shared
`TILELANG_019_SKIP_REASON` in two conftests
- AC-label strips in 9 test files / 1 bench /
`scripts/validate_manifest.py`
- `TODO(#NNNN)` strips in `tileops/kernels/convolution.py` (×2),
`reduction/logsumexp.py`, `reduction/softmax.py`
- Historical PR refs in `tests/test_validate_manifest.py` rewritten to
describe the contract
Excluded: `tileops/manifest/`, `docs/`, `.claude/` (out of rule scope).
`FIXME(staged-rollout)` blocks already compliant. Single-digit "Finding
#N" labels not matched by the rule's `#[0-9]{3,}` regex.
## Test plan
- [x] discovery scan returns 0 matches
- [x] `pytest tests/ --co -q` — 2752 collected
- [x] touched test files: 186 + 53 passed
- [x] pre-commit clean
Co-authored-by: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com>
|
|
|
|
1e8f091ec5
|
[Refactor][Manifest] drop eager ops import from tileops package (#1219)
Closes #1216 ## Summary - Drop `from . import ops` (and the matching `__all__` entry) from `tileops/__init__.py`. Importing `tileops` no longer pulls in `tileops.ops` or any `tilelang.*` module as a side effect. - Replace the `importlib.spec_from_file_location` workaround in `scripts/validate_manifest.py` with a direct `from tileops.manifest.shape_rules import dim_range_validity, dim_uniqueness, reduced_axes`. Drop the `_SHAPE_RULES_PATH` / `_shape_rules_spec` / `_shape_rules_module` plumbing and the underscore-prefixed rebindings. ## Test plan - [x] AC-1: Modified files pass existing tests. - [x] AC-2: `import tileops` does not load `tileops.ops` (verified via `sys.modules` snapshot). - [x] AC-3: `from tileops.manifest import shape_rules` does not load `tileops.ops` or any `tilelang.*` module (verified via `sys.modules` snapshot). - [x] AC-4: `scripts/validate_manifest.py` uses standard `from tileops.manifest.shape_rules import ...` instead of `importlib.spec_from_file_location`; the `_SHAPE_RULES_PATH` literal is removed. - [x] AC-5: `python scripts/validate_manifest.py` exit 0 on the checked-in manifest, output bit-identical to pre-change baseline. |
|
|
|
b81001dae7
|
[Feat][Manifest] AST-walk L0 typo defense for shape_rule callables (#1217) (#1218)
Closes #1217 ## Summary - Add `_check_shape_rule_callables` helper in `scripts/validate_manifest.py` that AST-walks each `shape_rules` entry at L0 and rejects any direct `Name(...)` call whose name is not in `_SHAPE_RULE_BUILTINS`. - SyntaxError in a shape_rule now surfaces as a single `[schema]` error at L0 instead of an L2 eval-time warning, so typos are caught without L2 mock-input setup. - Add four `TestSchema` cases covering unknown callable rejection, known-callable acceptance, attribute-call passthrough, and SyntaxError rejection. ## Test plan - [x] AC-1 — Modified files pass existing tests. (`python -m pytest -q tests/test_validate_manifest.py` → 185 passed) - [x] AC-2 — Typoed shape_rule callable names surface as `[schema]` errors at L0, not L2 NameError warnings. - [x] AC-3 — Existing `tileops/manifest/*.yaml` shape_rules pass the new L0 check with no false positives. (`python scripts/validate_manifest.py` → exit 0) - [x] AC-4 — Test coverage for unknown callable, known callables, attribute call, and syntax error cases. - [x] AC-5 — SyntaxError in a shape_rule moves from L2 eval warning to L0 schema-level rejection without L2 mock input setup. ## Test node delta ``` File Base HEAD Delta -------------------------------------------------------- tests/test_validate_manifest.py 181 185 +4 -------------------------------------------------------- TOTAL 181 185 +4 Growth: +2.2% ``` **Justification:** Four new tests are required by AC-4, one per shape_rule callable case (unknown name → reject, known names → pass, attribute call → pass, SyntaxError → reject). Each case exercises a distinct branch of the new AST walk in `_check_shape_rule_callables`; collapsing them would lose coverage of the L0/L2 boundary that this PR establishes. --------- Co-authored-by: Ibuki — a wind born from GPTs <Ibuki-wind@users.noreply.github.com> |
|
|
|
f851936a0a
|
[Refactor][Manifest] introduce shape_rules helpers as flat shape_rule builtins (#1210)
Closes #1209. Follow-up #1212 migrates `tileops/manifest/reduction.yaml` to reference these helpers. ## Summary Tooling-only PR — no `tileops/manifest/*.yaml` data is modified. - `tileops/manifest/shape_rules.py`: three pure-Python helpers — `dim_range_validity(x, dim) -> bool`, `dim_uniqueness(x, dim) -> bool`, `reduced_axes(x, dim) -> frozenset` — preserving the inline-form malformed-dim semantics verbatim (TypeError on malformed input is classified by the validator as an eval-error warning, bit-identical pre/post). - `scripts/validate_manifest.py`: helpers wired into `_SHAPE_RULE_BUILTINS` alongside `len`, `range`, `broadcast_shapes`, etc. Callable by bare name from any shape_rule body. The dict is built from an explicit `(name, callable)` list so a duplicate-name registration fails the validator at import time instead of silently shadowing. - `.claude/domain-rules/`: drift-surface cleanup — trim reduction-dim rule, dedupe Op-naming rule across three files, strip restatements that the validator/type-system already enforces. - `tests/test_validate_manifest.py`: drop per-case scaffolding tests; fold pre-existing `TestShapeRuleBroadcastBuiltins` per-case property tests into matrix tests. ## Test plan - [x] `pytest tests/test_validate_manifest.py -q` → 181 passed - [x] `python scripts/validate_manifest.py` → exit 0; pre/post diff empty (pinned by `test_sum_rules_helper_inline_classification_parity`) - [x] `tileops.manifest.shape_rules` exposes the three helpers via `__all__` with docstrings - [x] Helpers callable by bare name from shape_rules eval scope (pinned by `test_shape_rules_helpers_callable_by_bare_name`, driven by `__all__`) - [x] `_SHAPE_RULE_BUILTIN_PAIRS` raises on duplicate names (pinned by `test_shape_rule_builtin_pairs_have_unique_names`) - [x] `.claude/domain-rules/manifest-spec.md` reduction-dim rule points at `tileops.manifest.shape_rules` ## Test node delta `tests/test_validate_manifest.py`: 190 → 181 (−9). +6 contract-level tests for the new helper layer; −15 from folding `TestShapeRuleBroadcastBuiltins` per-case tests into matrix tests with no contract loss. --------- Co-authored-by: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com> |
|
|
|
8f6f1462b1
|
[Feat][Manifest] align where/clamp/masked_fill multi-input ops to pytorch api (#1109)
Closes #1088 ## Summary - Align the three multi-input elementwise op manifest entries (`where`, `clamp`, `masked_fill`) to PyTorch's public API, per the manifest trust model. - Split families per the "No `Optional[Tensor]`" rule: `Clamp` family becomes 4 entries (`ClampFwdOp` primary + `ClampScalarFwdOp` / `ClampMinFwdOp` / `ClampMaxFwdOp` variants); `MaskedFill` family becomes 2 entries (`MaskedFillFwdOp` primary + `MaskedFillScalarFwdOp` variant). - Update `WhereFwdOp.shape_rules` to use `broadcast_shapes(condition.shape, input.shape, other.shape)` matching `torch.where` broadcasting; remove inline BLOCKED notes from all three families. - Add `broadcast_shapes` and `is_broadcastable_to` to `_SHAPE_RULE_BUILTINS` in `scripts/validate_manifest.py` (pure-Python, no torch dependency) so manifest `shape_rules` can express NumPy/PyTorch broadcast semantics. - Add roofline blocks for the new `Clamp` and `MaskedFill` variants, with `vars.N_total = product(broadcast_shapes(...))` for broadcasting variants and `product(input.shape)` for the scalar-bound `ClampScalarFwdOp`. - All new / restructured entries land as `status: spec-only`; kernel and op-class conformance is tracked in follow-up #1107. ## Test plan - [x] AC-1: `WhereFwdOp.shape_rules` uses `broadcast_shapes(...)` and matches `torch.where` broadcasting semantics; inline BLOCKED note removed. - [x] AC-2: Clamp family expressed as 4 entries (`ClampFwdOp` primary + `ClampScalarFwdOp` / `ClampMinFwdOp` / `ClampMaxFwdOp` variants) with `Number | None` for scalar params; inline BLOCKED note removed. - [x] AC-3: MaskedFill family expressed as 2 entries (`MaskedFillFwdOp` primary + `MaskedFillScalarFwdOp` variant) with bidirectional input/mask broadcasting (out-of-place `Tensor.masked_fill` semantics) and `Number` value type; inline BLOCKED note removed. - [x] AC-4: `_SHAPE_RULE_BUILTINS` includes `broadcast_shapes` and `is_broadcastable_to`, with unit tests in `tests/test_validate_manifest.py`. - [x] AC-5: `scripts/validate_manifest.py --levels schema` passes for all affected entries. - [x] AC-6: `where_fwd_roofline` and new variant roofline blocks are consistent with the post-broadcast `N_total` convention. - [x] AC-7: Follow-up issue filed (#1107) tracking kernel + op-class conformance to all new / restructured entries. Verification commands (all green locally): - `pre-commit run --all-files` → all hooks pass - `python scripts/validate_manifest.py --levels schema` → all manifest checks passed - `pytest tests/test_validate_manifest.py` → 190 passed, 3 warnings ## Test node delta ``` File Base HEAD Delta -------------------------------------------------------- tests/test_validate_manifest.py 171 190 +19 -------------------------------------------------------- TOTAL 171 190 +19 Growth: +11.1% ``` **Justification:** The +19 nodes cover the new `_SHAPE_RULE_BUILTINS` helpers (`broadcast_shapes`, `is_broadcastable_to`): direct unit coverage of helper behavior (broadcasting equivalence, broadcast failures, scalar/empty-shape edge cases, unidirectional broadcastability) plus end-to-end coverage via `_eval_shape_rule` to confirm the helpers are actually reachable from manifest `shape_rules` strings. These helpers gate the new PyTorch-aligned `shape_rules` for `where`, `clamp`, and `masked_fill`; without this coverage, the broadcasting semantics of those entries are validator-untested. --------- Co-authored-by: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com> |
|
|
|
46d0918a79
|
[Refactor][Manifest] split elementwise.yaml into per-sub-family shards (#1095)
Closes #1093 ## Summary - Split `tileops/manifest/elementwise.yaml` (62 ops, ~2050 lines) into 4 sub-family shards by signature shape: - `elementwise_unary_math.yaml` — 24 ops (~746 lines): unary math (single-input single-output, math kernels) - `elementwise_unary_activation.yaml` — 13 ops (~485 lines): unary activations (single-input single-output, NN activations) - `elementwise_binary.yaml` — 23 ops (~739 lines): binary (two-input single-output) - `elementwise_multi_input.yaml` — 2 ops (~84 lines): 3+ input or multi-output - Removed the monolithic `tileops/manifest/elementwise.yaml`; entries moved verbatim via `ruamel.yaml` round-trip, preserving `family: elementwise` on every entry. - Updated `tileops/manifest/__init__.py` module docstring and `docs/manifest.md` / `CLAUDE.md` to describe the sharded layout. No op-code or kernel-code changes — pure manifest file partitioning. Total op count stays at 114; `load_manifest()` / `load_workloads()` behavior is dict-equal to upstream/main. ## Test plan - [x] AC-1: `tileops/manifest/elementwise.yaml` removed; ops live in 4 sub-files, every shard < 1000 lines and ≤ 30 ops (largest: 746 lines / 24 ops). - [x] AC-2: `load_manifest()` returns the same 114 op names with dict-level content equal to upstream/main (verified via Python comparison: `name_sets_equal=True`, `content_equal=True`). - [x] AC-3: `python scripts/validate_manifest.py` exits 0 — "All manifest checks passed" (58 warnings, unchanged from baseline). - [x] AC-4: `pytest tests/test_ops_manifest.py tests/test_validate_manifest.py tests/test_workloads_to_params.py` → 198 passed, matching upstream/main baseline (198 passed) at branch point. - [x] pre-commit passed on all changed files. ## Additional context Follows the same trust-model rules as PR #1092 (which sharded the manifest into one-file-per-family): manifest-only PR, no op or kernel changes bundled. The elementwise family was the only family still over the ~1000-line / ~30-op soft cap after #1092 — this PR completes the per-sub-family layout. --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
9448edbfba
|
[Refactor][Manifest] flatten yaml top-level ops key (#1094)
## Summary
- Each family yaml previously wrapped its entries under a top-level
`ops:` key, costing one indent level on every entry without serving any
purpose — family scope is already conveyed by the filename, and the
loader just unwrapped the key.
- Flatten the format: each family file is now a direct mapping of op
name → entry. Updated 8 yaml files (4546 lines), the `tileops.manifest`
loader, the `--manifest-path` branch in `scripts/validate_manifest.py`,
the `tests/test_validate_manifest.py` fixtures, and the example in
`docs/manifest.md`.
- Pre-release project, so no compat shim — the old format is no longer
accepted.
## Test plan
- [x] pre-commit passed (full hook suite on all changed files)
- [x] `python scripts/validate_manifest.py` → `All manifest checks
passed.`
- [x] `pytest tests/test_validate_manifest.py
tests/test_gpu_smoke_policy.py` → 176 passed
- [x] `from tileops.manifest import load_manifest` smoke check → 114 ops
loaded
## Regression
- Loader migration is mechanical (`data.get("ops") or {}` →
`yaml.safe_load(...) or {}`). All 114 ops load identically; downstream
consumers (`load_workloads`, `validate_manifest`) untouched.
- `_load_manifest` private alias removed (no remaining callers in repo).
## Additional context
There is no near-term plan for family-level metadata. If that need
arises later, a wrapper can be reintroduced — YAGNI for now.
---------
Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com>
|
|
|
|
19ee4c2b18
|
[Refactor][Manifest] split monolithic yaml into per-family package (#1092)
## Summary - Split `tileops/ops_manifest.yaml` (monolithic, 4564 lines) into one YAML file per op family under `tileops/manifest/` — `elementwise.yaml` (62), `reduction.yaml` (19), `attention.yaml` (12), `normalization.yaml` (10), `moe.yaml` (7), `scan.yaml` (2), `convolution.yaml` (2). - Convert `tileops/manifest.py` into the `tileops/manifest/` package; `__init__.py` scans the directory, merges all `ops` dicts, errors on duplicate keys, caches with `lru_cache(1)`. Public API (`load_workloads`) is unchanged; new helpers `load_manifest()` and `manifest_files()` exposed. - Update direct consumers: `scripts/validate_manifest.py` (default load via package; `manifest_path=` kept for synthetic fixtures), `tests/test_ops_manifest.py` (fixtures via `load_manifest()`), `pyproject.toml` package-data, `.github/workflows/preflight.yml` path globs. - Synchronize all documentation: 22 `.md` files (CLAUDE.md, docs/, .claude/skills/, .claude/domain-rules/, .claude/rules/) plus docstrings in 5 benchmark files and `tileops/perf/formulas.py`. CLAUDE.md "Reading the ops manifest" section and docs/manifest.md "Layout" section rewritten for the new structure. `add-manifest` / `fix-manifest` skills note family-file routing. ## Test plan - [x] `python scripts/validate_manifest.py` passes - [x] `pytest tests/test_ops_manifest.py tests/test_validate_manifest.py tests/test_workloads_to_params.py` — 197 passed - [x] pre-commit passed - [x] `python3 -c "from tileops.manifest import load_manifest; assert len(load_manifest()) == 114"` — round-trip preserves all 114 ops, no duplicates ## Additional context The split keeps the conceptual "single source of truth" (the merged `ops` dict) while addressing the practical pain of one 4500+ line file. Largest family file is `elementwise.yaml` at 2030 lines (62 ops); a follow-up PR can sub-split elementwise into unary/binary/multi-input if desired. No op or kernel code is touched, so this PR does not conflict with the trust-model rule prohibiting joint op+manifest changes. ## Structural Readiness All checks passed. ## Follow-up Issues: - #1093 — sub-split elementwise.yaml (2030 lines / 62 ops) into unary/binary/multi-input sub-files. --------- Co-authored-by: Ibuki 🍃 — a wind born from GPTs <Ibuki-wind@users.noreply.github.com> |
|
|
|
b913e9cd72
|
[Feat][TOOLING] validator parity for _infer_output_shapes and _validate_dtypes (#1005)
## Summary Add validator checks that ensure a concrete op's `_infer_output_shapes` and `_validate_dtypes` agree with its manifest declaration. Introduces L2 infer-shape parity and L3 dtype parity checks in `scripts/validate_manifest.py`, plus documents the two new rows in the Consistency Enforcement table and the `parity_opt_out` entry field. Closes #994 ## What this PR does - **L2 infer-shape parity**: for each `status: implemented` op, invokes `_infer_output_shapes` with concrete mock inputs and checks the returned shapes against `signature.outputs[*].shape` declarations, `signature.static_dims`, param defaults, and `shape_rules` (incl. R11a comprehensions). Symbolic dims (`B`, `S`, `H`, `D`, …) are bound from input shapes and also rebound from inferred outputs so output-only symbols like conv `L_out` can be checked against rule-derived values. - **L3 dtype parity**: validates `_validate_dtypes` behaviour against `dtype_combos` (when present, exhaustive) or declared `dtype` unions (full Cartesian product), plus out-of-union negative probes and `same_as(ref)` identity probes on both branches. - **Manifest-data validation (runs unconditionally in `check_l3`)**: combo completeness (every combo row must cover every declared input), invalid dtype tokens, unresolved / cyclic `same_as(ref)`, and union values in combo entries are hard L3 errors regardless of whether the class overrides `_validate_dtypes`. - **`parity_opt_out`**: optional manifest entry field (`true` or subset of `[shape_parity, dtype_parity]`) to suppress parity checks for ops whose method genuinely needs GPU execution. Documented in `docs/manifest.md` (Entry Structure) and `docs/ops-design-reference.md` (Consistency Enforcement). - **Security / bounds**: `eval()` for shape rules is restricted to a builtins allowlist, AST-filtered for dunder attrs, and protected from `ctx` overwriting `__builtins__`. Cartesian product iteration is bounded by `_MAX_DTYPE_COMBOS = 4096` with deterministic skip + warning on over-bound ops. - **Policy (tightened over review rounds)**: unexpected body exceptions from `_infer_output_shapes` / `_validate_dtypes` are **hard parity errors by default**; reserve the soft-warning path for entries that explicitly opt out. Signature-bind failures (via `inspect.signature().bind`) remain a separate category. ## Test plan - [x] **AC-1**: Modified files pass unit tests. - Evidence: `python -m pytest tests/test_validate_manifest.py -q` → 170 passed. - [x] **AC-2**: Validator L2 reports error when `_infer_output_shapes` disagrees with `shape_rules` / declared shapes. - Evidence: `TestInferShapeParity` covers incorrect infer, symbolic dim mismatch, R11/R11a helper rules, input-only precondition separation, conv-like output-only symbols, declared-output-shape exact match, `self.attr` / `self.static_dim` mock populations, body-exception-as-hard-error (+ opt-out downgrade). - [x] **AC-3**: Validator L3 reports error when `_validate_dtypes` accepts non-listed or rejects declared combos. - Evidence: `TestValidateDtypesParity` covers union accept/reject, `dtype_combos` accept/reject listed, first-rejected-later-accepted enumeration, out-of-union probes (both branches), `same_as` identity probes, signature-mismatch vs body-exception separation (body errors are hard unless opted out), Cartesian bound skip with warning, exhausts-the-union warning. - [x] **AC-4**: Consistency Enforcement table adds two new rows. - Evidence: `docs/ops-design-reference.md` adds rows for `_infer_output_shapes` vs `shape_rules` (L2) and `_validate_dtypes` vs `dtype_combos` / unions (L3), plus a "Parity check coverage" paragraph documenting missing-override warning + `parity_opt_out`. `docs/manifest.md` Entry Structure documents the `parity_opt_out` field. ## Integration check ``` python scripts/validate_manifest.py → exit 0, 0 ERROR lines, 48 WARNING lines (expected: missing-override warnings for implemented ops awaiting codegen migration + pre-existing bench warnings). "All manifest checks passed." ``` No existing Op required `parity_opt_out` under the tightened body-exception policy. ## Test node-delta (testing-budget rule) ``` tests/test_validate_manifest.py 101 170 +69 (+68.3%) ``` Justification: the delta covers AC-1..AC-4 parity behaviour plus regression coverage from seven review rounds: - signature-bind vs body-exception separation (body errors are hard L2/L3 by default, opted out via `parity_opt_out`) - `same_as` fixpoint resolution (order-independent); pure-cycle and dangling-ref diagnosis produce hard L3 errors with all cycle participants named - out-of-union negative probes on both branches (`dtype_combos` and Cartesian), sourced from `_TORCH_DTYPES − declared` so sentinel engulfment can't create a vacuous pass; full-coverage case emits a named warning - `same_as(ref)` identity probes on both branches - mock `self` via `cls.__new__(cls)` with `signature.params` defaults, `static_dims` resolved values, and primary `dtype` axis populated - symbolic dim binding from `signature.*.shape`; output-only symbol rebinding from inferred outputs (conv-like `L_out` scenarios) - `static_dims` and scalar-int param defaults both pin declared-output-shape positions to exact values (not just rank/consistency) - combo completeness (every row covers every declared input), invalid dtype value rejection, union-in-combo-value rejection as hard L3 manifest errors — run unconditionally in `check_l3`, independent of `_validate_dtypes` override status - Cartesian product bounded by `_MAX_DTYPE_COMBOS = 4096` with deterministic skip + warning - `eval()` sandbox: restricted `__builtins__`, AST dunder-attr filter, `ctx` cannot overwrite `__builtins__` Tests are validator regressions, not broad combinatorics. --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
a3b2af0a79
|
[Refactor][Roofline] remove manifest-level roofline evaluator (#1024)
## Summary
This PR now targets the design direction that target design should not
have a manifest-level evaluator. Instead of keeping a legacy evaluator
boundary, it removes the legacy evaluator surface and migrates current
callers.
- Delete `tileops/manifest_legacy_roofline.py`.
- Remove manifest exports for `_safe_eval`, `eval_roofline`,
`resolve_roofline_vars`, and `has_roofline_vars`.
- Move benchmark roofline consumption to op-local `eval_roofline()` and
`ManifestBenchmark(op_name, op, workload)`.
- Add local `eval_roofline()` implementations for the currently migrated
norm, MoE, reduction, and softmax-family benchmark surfaces.
- Update validator, tests, and roofline docs for the op-local evaluator
contract.
## Test Plan
- `ruff check` on changed Python files
- `python -m compileall tileops benchmarks scripts tests`
- `pytest tests/test_ops_manifest.py tests/test_workloads_to_params.py
tests/test_validate_manifest.py
benchmarks/tests/test_roofline_workload_protocol.py`
- `python scripts/validate_manifest.py`
Note: `validate_manifest.py` passes and still reports the existing
attention benchmark L4 warnings; those are pre-existing warnings outside
this cleanup scope.
## Follow-up
No follow-up issues or suggestions.
---------
Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com>
|
|
|
|
4da2dbba1d
|
[CI] Run nightly inside containerized runner (#1017)
Closes #1016 ## Summary - Move nightly workflow execution to assume it already runs inside the containerized self-hosted runner. - Remove workflow-level Docker startup, workspace ownership fixes, and the local tileops-runner:latest wrapper. - Add a nightly runner verification script for cache env vars, cache writability, and GPU visibility. ## Test plan - [x] pre-commit passed during git commit - [x] bash -n scripts/ci/verify_nightly_runner.sh - [x] python -c "import yaml; yaml.safe_load(open('.github/workflows/nightly.yml')); print('yaml ok')" - [x] git diff --check -- .github/workflows/nightly.yml scripts/ci/verify_nightly_runner.sh - [x] rg -n "docker run|tileops-runner:latest|Fix workspace ownership|/data/ci-cache" .github/workflows/nightly.yml returns no matches - [ ] actionlint .github/workflows/nightly.yml (not run locally; actionlint is not installed) ## Additional context This PR intentionally keeps the existing runner labels unchanged and does not modify runner-maintenance.yml. Cache cleanup policy is left for a separate follow-up, as scoped in #1016. |
|
|
|
ee28b2926b
|
[Fix][CI] widen fork-PR gpu-smoke cache hits without widening write surface (#1009)
## Summary
Maximize cache hit rate for fork-PR `gpu-smoke` runs without widening
the write-path attack surface.
Three narrowly-scoped fixes:
1. **Narrow venv HASH** (`scripts/ci_venv_hash.py`): parse
`pyproject.toml` with `tomllib` and hash only `[project]` +
`[build-system]` + `[project.optional-dependencies]`. Edits to
`[tool.pytest]` / `[tool.ruff]` no longer bust the venv cache. Falls
back to full-file hash on parse failure, preserving the 16-char hex
format.
2. **Member-fork → trusted runtime path**
(`.github/workflows/gpu-smoke.yml` policy): when head repo differs from
base but `author_association ∈ {OWNER, MEMBER, COLLABORATOR}`, set
`is_fork=false` so the PR traverses the trusted runtime path
(`RUNTIME_ROOT=/home/ci-runner`). External contributors still go the
isolated path.
3. **Skip in-line atomic age-trim**
(`.github/actions/reclaim-runner-disk/action.yml`): add
`skip-atomic-age-trim` input (default `false`); `gpu-smoke.yml` passes
`true` so the destructive autotuner age-trim only runs via
`runner-maintenance.yml` (daily). Sentinel-repair remains always on.
Closes #1007
## Test plan
- **AC-1**: Changing `[tool.pytest]` / `[tool.ruff]` sections of
`pyproject.toml` does NOT change the computed venv HASH — pass. `python
-m pytest -q tests/test_ci_venv_hash.py tests/test_gpu_smoke_policy.py
tests/test_reclaim_action.py`: 26 passed in 1.20s. Direct hash probe:
base=`c2c5efe07bd7e601`, tool-section mutation=`c2c5efe07bd7e601`,
stable=True.
- **AC-2**: Changing `[project.dependencies]` or
`[build-system].requires` DOES change the computed HASH — pass.
Dependency mutation `d435cbfc2b7aca79` changed=True; build-system
requires mutation `93760788d3b9ecd3` changed=True.
- **AC-3**: Member-fork PRs produce `is_fork=false` and traverse the
trusted runtime path — pass. Policy probe with `HEAD_REPO!=BASE_REPO`
and `AUTHOR_ASSOC=MEMBER` produced `is_fork=false`; Resolve-runtime
probe with `IS_FORK=false` exported `RUNTIME_ROOT=/home/ci-runner`.
Negative probe with `AUTHOR_ASSOC=CONTRIBUTOR` produced `is_fork=true`
and isolated `RUNNER_TEMP` runtime.
- **AC-4**: `gpu-smoke` in-line `reclaim-runner-disk` does NOT invoke
atomic age-trim — pass. Composite-action run with
`SKIP_ATOMIC_AGE_TRIM=true` emitted `Skipping atomic age-trim (opted
out)`, still ran sentinel-repair on half-dead atomic subdir, and
preserved the complete atomic subdir.
- **AC-5**: `runner-maintenance.yml` daily job still performs full
atomic trim — pass. YAML probe confirmed reclaim step has
`force-reclaim=true` and `skip-atomic-age-trim` absent/false. `git diff
main...HEAD` does not modify `runner-maintenance.yml`.
- **AC-6**: Unit tests for `reclaim_cache.sh` still pass — pass.
`tests/test_reclaim_action.py` Base 13 nodes, HEAD 13 nodes, Delta 0;
all 13 sentinel-repair tests pass.
---------
Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com>
|
|
|
|
a5e3c48f49
|
[Design][Manifest] Redesign init_dims as static_dims (docs + schema + validator) (#982)
## Summary
Complete the `static_dims` redesign (renamed from `init_dims`) for
arbitrary-rank ops in a single internally consistent PR. Scope now
covers:
1. **Docs** — authoritative spec rewrite: `docs/manifest.md` R20,
`docs/ops-design.md` `_cache_key` section,
`docs/ops-design-reference.md` Base Class Protocol + Consistency
Enforcement table.
2. **Manifest yaml** — 16 ops migrated from `init_dims: {N: {from:
"..."}}` to flat `static_dims: {N: "..."}`. LogSumExpFwdOp's multi-axis
form removed (violates new R20 single-axis rule).
3. **Validator** — `scripts/validate_manifest.py` renamed
`manifest_init_dims` → `manifest_static_dims`, reads
`sig.get("static_dims")`, updated error messages. L1 enforces
static_dims keys ⊆ `__init__` parameters.
4. **Status flips** — 13 `status: implemented` ops flipped to `status:
spec-only` with per-op comments explaining the impl/spec mismatch
(LayerNorm family × 5, BatchNorm/InstanceNorm × 3,
Softmax/LogSoftmax/LogSumExp × 3, Argmax/Argmin × 2). This is the
trust-model-prescribed response to impl divergence.
5. **PyTorch API alignment** — `dim` types include `tuple[int, ...]`;
`isinstance(dim, list)` → `(list, tuple)` throughout manifest;
LogSumExpFwdOp `shape_rules` dim-normalized.
6. **Design-first principle** — one-line statement added to `CLAUDE.md`
(always loaded). Docs focus on technical spec.
Closes #984 (absorbed). Closes #976.
#985 remains open for the Op base `_cache_key` runtime +
empty-static_dims warning.
## Test plan
- [x] `python scripts/validate_manifest.py` passes (24 pre-existing
bench-file warnings, 0 errors)
- [x] 91 validator unit tests pass (4 new `static_dims` tests)
- [x] No `init_dims` remnants anywhere: `grep -rn init_dims docs/
tileops/ scripts/ tests/` returns empty
- [x] 16 `static_dims:` entries in manifest yaml (down from 31
`init_dims` originally — 14 dim=None-supporting reduction ops correctly
had theirs removed per R20 applicability, LogSumExp removed per
single-axis rule)
- [x] `docs/manifest.md` R20 uses `static_dims` flat syntax with
single-axis reference rule, two time points + codegen pseudocode,
LinearFwdOp multi-input example, empty-legal + mandatory `_cache_key`
override (SumFwdOp example)
- [x] Design-first principle stated once in `CLAUDE.md` Project Overview
- [x] Consistency Enforcement table lists only live enforcement
(validator + runtime); deferred mechanisms tracked via issues
## Follow-up
Issues:
- #992 — dim-aware ManifestBenchmark + end-to-end roofline.vars
consumption
- #993 — retrofit 13 spec-only ops to the new static_dims / _cache_key
contract
- #994 — validator codegen parity checks for _infer_output_shapes /
_validate_dtypes
---------
Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com>
|
|
|
|
bdbd7a8324
|
[Refactor] Rename base modules to <module>_base.py convention (#950)
## Summary Unify base module file naming to `<module>_base.py` convention across four modules: - `workloads/base.py` -> `workloads/workload_base.py` - `tileops/ops/op.py` -> `tileops/ops/op_base.py` - `tileops/kernels/kernel.py` -> `tileops/kernels/kernel_base.py` - `benchmarks/benchmark.py` -> `benchmarks/benchmark_base.py` All imports, string references, and doc paths updated. No logic changes — pure rename + import propagation. Closes #947 ## Test plan - [x] **AC-1**: All four files renamed to <module>_base.py -- pass - [x] **AC-2**: All imports updated, no broken references -- pass - [x] **AC-3**: docs/testing.md path references updated -- pass - [x] **AC-4**: Pre-commit and existing tests pass -- pass **Test results**: 2368/2368 tests passed, 0 failed --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
ef5d248f2f
|
[Refactor][Benchmark] Extract ManifestBenchmark base class and shared helpers (#915)
## Summary Extract a generic `ManifestBenchmark` base class and shared helpers (`roofline_vars`, `workloads_to_params`) into `benchmarks/benchmark.py`, so manifest-driven bench files only specify the op name and baseline function per test. This eliminates ~300 lines of duplication across 5 benchmark files. Closes #904 ## Changes - **benchmarks/benchmark.py** — Add `ManifestBenchmark(op_name)` subclass of `BenchmarkBase` with `roofline_vars(workload)` and `workloads_to_params(op_name)` public helpers - **benchmarks/ops/bench_{softmax,reduce,argreduce,logical_reduce,vector_norm}.py** — Refactor to use `ManifestBenchmark`, replacing 19 per-op benchmark classes - **scripts/validate_manifest.py** — Teach AST validator to recognize `ManifestBenchmark` subclasses as satisfying `eval_roofline` requirement - **tests/test_validate_manifest.py** — Add tests for indirect bench-validator AST paths and a regression test documenting the subclass override limitation ## Test plan - [x] AC-1: All 5 bench files use `ManifestBenchmark` instead of per-op classes - [x] AC-2: `_roofline_vars` and `_workloads_to_params` exist only in `benchmarks/benchmark.py`, not duplicated - [x] AC-3: `pytest --collect-only` passes for all 5 bench files (63 tests collected) - [x] AC-4: `validate_manifest.py --check-op` produces zero `[bench]` warnings for all 19 affected ops - [x] 89 validator tests pass, lint clean ## Follow-up - #917 — Add import alias and attribute access support to bench AST validator - #918 — Type-narrow roofline_vars parameter from WorkloadBase to typed Protocol --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
d81ec1f5ef
|
[Test][Softmax] Add non-aligned edge-case shapes for softmax-family ops (#914)
## Summary Add targeted edge-case test shapes that exercise the M×N non-aligned combination for softmax-family kernels, covering single-tile and multi-tile (large-N) paths. Closes #906 ## Changes **Tests** (`tests/ops/test_softmax.py`): - 3 new shapes per fixture (SoftmaxFixture, LogSoftmaxFixture, LogSumExpFixture): - `(33, 300)` fp32 — both M and N non-aligned, single-tile path - `(33, 33000)` fp16 — both M and N non-aligned, multi-tile with masked loads - `(33, 32768)` fp16 — non-aligned M with large-N tiled path **Scripts** (`scripts/test_node_delta.py`): - Fix base-ref collection: write temp file in original directory instead of `/tmp/` so pytest can resolve project imports **Foundry** (`.foundry/mold/pre-create-checks.md`): - Add test node delta as pre-create check #3 (soft gate) with correct `--base upstream/main` command ## Test plan - [x] AC-1: All 126 tests pass (`pytest tests/ops/test_softmax.py`) - [x] AC-2: New shapes exercise non-aligned M×N paths (single-tile and multi-tile) ## Test node delta ``` File Base HEAD Delta -------------------------------------------------- tests/ops/test_softmax.py 117 126 +9 -------------------------------------------------- TOTAL 117 126 +9 Growth: +7.7% ``` Justification: 9 new `pytest.mark.full` nodes (3 shapes × 3 fixtures). Each shape targets a distinct non-aligned code path not covered by existing aligned shapes. No cross-dtype expansion (existing fixtures already cover fp32/fp16/bf16 on aligned shapes; the non-aligned path shares the same tiling/masked-load logic across fp16 and bf16). ## Follow-up No follow-up issues or suggestions. --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
0ce134bb24
|
[Refactor][Manifest] Enforce status as required and validate kernel_map schema (#888)
## Summary Update the manifest validator to enforce `status` as required and validate `kernel_map` schema. - `status` added to `_REQUIRED_TOP` — entries missing it produce a schema error - `status` must be a string (`"implemented"` or `"spec-only"`); null and non-string values rejected - `source.kernel_map` validated when present: must be `dict[str, str]` - Missing `kernel_map` for `status: implemented` entries produces a **warning** (not error) — full enforcement deferred to #887 which populates kernel_map for all existing ops - `_is_spec_only()` defaults to `True` on missing/non-string status (safe for `--levels` bypass) - Integration test validates against the real checked-in manifest ## Changed files - `scripts/validate_manifest.py` — status required, kernel_map validation, _is_spec_only safe default - `tests/test_validate_manifest.py` — 9 new tests, updated helper, integration test ## Test plan - [x] `pytest tests/test_validate_manifest.py` — 84 passed, 1 xfailed - [x] `python scripts/validate_manifest.py` exits 0 on real manifest (25 kernel_map warnings) - [x] Pre-commit hooks pass Closes #886 ## Follow-up - #887 — Populate kernel_map for all implemented ops (converts warnings to clean pass) No additional follow-up issues or suggestions. --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
c85712ec45
|
[Refactor][Manifest] Rename keys to PascalCase and add ref_api field (#869)
## Summary
Rename all manifest keys from snake_case to PascalCase class names in
`{Name}{Direction}Op` format and add the required `ref_api` field to
every op entry. Validator enforces exact `cls.__name__ == manifest_key`
identity — no heuristic resolution. No op/kernel/benchmark
implementation code is modified.
**Note**: After this PR merges, benchmark callers that use snake_case op
names (e.g. `load_workloads("rmsnorm_fwd")`) will break. PR-B (#866)
fixes all callers as part of the class rename.
Closes #865
## Test plan
- [x] All manifest keys are PascalCase class names in
`{Name}{Direction}Op` format
- [x] `ref_api` field required for all ops (`"none"` when no external
API counterpart)
- [x] Validator L0 schema enforces `ref_api` as required string
- [x] Validator requires exact `cls.__name__ == manifest_key` — no
single-candidate bypass, no heuristic fallback
- [x] No changes to any file under `tileops/ops/`, `tileops/kernels/`,
or `benchmarks/`
- [x] Integration test `xfail` until PR-B renames Op classes
## Changed files
- `tileops/ops_manifest.yaml` — renamed all 52 keys, added `ref_api` to
every entry
- `tileops/manifest.py` — canonical PascalCase keys only (no legacy
alias resolution)
- `scripts/validate_manifest.py` — `ref_api` required in L0 schema,
exact class-name identity enforcement
- `tests/test_ops_manifest.py` — updated test fixtures, canonical key
tests
- `tests/test_validate_manifest.py` — updated validator tests,
exact-match enforcement tests
---------
Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com>
|
|
|
|
dbf58a10b1
|
[Doc][Manifest] Clarify same_as(ref) as identity constraint with validator enforcement (#864)
## Summary Clarify \`same_as(ref)\` as a dtype-only identity constraint in manifest spec, and add validator enforcement. **Docs:** - \`docs/manifest.md\`: R3/R3a define \`same_as(ref)\` as exact dtype identity (not "dependent types"). R5/R8 clarify \`same_as\` is dtype-only — shape must be explicitly declared. R13 status default changed to \`spec-only\`. Overall doc reduced by ~27% (470→342 lines). - \`.claude/domain-rules/manifest-spec.md\`: matching updates — \`same_as\` dtype identity, shape explicit, status default \`spec-only\`. **Validator:** - \`_check_dtype_combos_same_as_identity()\`: enforces that \`dtype_combos\` entries assign the same dtype to \`same_as\`-bound tensors. Rejects partial combos where a bound tensor appears without its reference. - \`_is_spec_only()\`: defaults to \`spec-only\` when \`status\` is absent, matching R13. **Tests:** 5 new test cases — dtype_combos pass, mismatch, multi-binding mismatch, partial-combo rejection, missing-status default. Closes #863 ## Changes - \`docs/manifest.md\` — rewritten: concise, no "dependent types", \`same_as\` dtype-only, status default spec-only - \`.claude/domain-rules/manifest-spec.md\` — matching rule updates - \`scripts/validate_manifest.py\` — \`_build_same_as_map()\`, \`_check_dtype_combos_same_as_identity()\`, \`_is_spec_only()\` default fix - \`tests/test_validate_manifest.py\` — 5 new tests (81 total, all pass) ## Test plan - [x] AC-1: \`docs/manifest.md\` R3/R3a explicitly defines \`same_as(ref)\` as dtype identity constraint - [x] AC-2: Validator flags \`dtype_combos\` with mismatched or partial \`same_as\`-bound entries - [x] AC-3: Full manifest validation passes (\`python scripts/validate_manifest.py\` → 0 errors) ## Follow-up No follow-up issues or suggestions. --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
a93643998e
|
[Refactor][Validator] Deduplicate PascalCase resolution loops in _resolve_op_class (#836)
## Summary Consolidates the duplicated resolution heuristics in `_resolve_op_class` into a single strategy loop and adds ambiguity detection to all matching stages (not just the terminal fallback). - Three separate PascalCase loops (stripped_pascal, full_pascal, suffix_fwd_bwd) replaced with single `for`-loop over an ordered strategies list - Suffix matching (`_fwd`/`_bwd`) now emits an ambiguity warning when multiple candidates match, consistent with the pattern established in PR #830 - One new test (`test_suffix_match_ambiguity_emits_warning`) covers the multi-candidate suffix case Closes #811 ## Acceptance Criteria | ID | Criterion | Status | |----|-----------|--------| | AC-1 | Single resolution loop replaces the duplicated PascalCase loops | PASS | | AC-2 | _fwd/_bwd suffix matching emits ambiguity warning when multiple candidates match | PASS | | AC-3 | All existing TestResolveOpClass tests pass unchanged | PASS | | AC-4 | New test covers suffix-match ambiguity (multiple fwd candidates) | PASS | | AC-5 | Full manifest validation passes | PASS | ## Test plan - [x] 25 TestResolveOpClass tests pass (24 pre-existing + 1 new) — 2.93s - [x] Full test suite: 75 passed, 0 failed - [x] `python scripts/validate_manifest.py` — all manifest checks passed - [x] pre-commit lint passes (ruff, codespell, mdformat) ## Follow-up No follow-up issues or suggestions. --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
e814e01bf9
|
[Fix][Validator] Emit warning on ambiguous op class resolution instead of silent fallback (#830)
## Summary When `_resolve_op_class` finds multiple candidate classes but no naming heuristic matches, it previously fell back silently to `candidates[0]` (alphabetical order). This could produce wrong validation results without any diagnostic. This PR replaces the silent fallback with a `UserWarning` that identifies the op name, all candidate class names, and the file path, then returns an unresolved result (`cls=None`) so the caller can handle it explicitly. Closes #810 ## Test plan - [x] AC-1: When all heuristics fail, a warning or error is emitted identifying the ambiguous resolution - [x] AC-2: Full manifest validation passes (no existing ops broken) — `python scripts/validate_manifest.py` -> All checks passed - [x] AC-3: Unit test covers the ambiguous-fallback code path — `test_ambiguous_fallback_returns_none_with_warning` added **Test results**: 79 tests passed, 0 failed ## Follow-up - #811 — Consolidate resolution heuristics + fix `_fwd`/`_bwd` suffix-match silent fallback Suggestions: strengthen ambiguous-fallback test to match full diagnostic fields (op_name, candidate_names, op_file) --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
2a9b0fad56
|
[Fix][Validator] Resolve op classes in multi-class files by PascalCase convention (#807)
## Summary Fix `_resolve_op_class` to correctly resolve op names to classes when source files contain multiple Op classes (e.g., `reduce.py` with 8 ops). The previous substring heuristic (`all parts in cls_lower`) was too permissive — `var_fwd` matched `AmaxOp` instead of `VarOp` because no class contained `"fwd"`, and the alphabetical fallback picked the wrong candidate. Closes #803 ## Changes ### Resolver heuristic rewrite (`scripts/validate_manifest.py`) - **Removed** the loose substring-based multi-candidate heuristic (`all(p in cls_lower for p in parts)`) - **Added** exact PascalCase match as the primary multi-candidate strategy: strip `_fwd`/`_bwd` suffix, convert remainder to PascalCase + `"Op"` (e.g., `var_fwd` → `VarOp`, `var_mean_fwd` → `VarMeanOp`) - **Added** full-name PascalCase fallback for Fwd/Bwd-suffixed class naming conventions (e.g., `batchnorm_fwd` → `BatchNormFwdOp`) - Retained suffix matching (`fwd`/`bwd` in class name) as final fallback ### Test coverage (`tests/test_validate_manifest.py`) - Added `TestResolveOpClass` class with 21 test cases: - 8 parametrized tests for `reduce.py` multi-class resolution (SumOp, MeanOp, AmaxOp, AminOp, ProdOp, VarOp, StdOp, VarMeanOp) - 11 parametrized tests for all single-file reduction ops (argmax, argmin, all, any, count_nonzero, l1_norm, l2_norm, inf_norm, softmax, log_softmax, logsumexp) - Single-class file resolution, import error handling, no-op-class handling ### Review fixes - Removed duplicate test method flagged by Copilot review - Fixed misleading comment example (`"batchnorm_fwd" → "BatchnormFwdOp"` → `"sum_fwd" → "SumFwdOp"`) ## Test plan - [x] **AC-1**: `_resolve_op_class` returns correct class for all 19 reduction ops (8 multi-class in `reduce.py` + 11 single-file) — verified by parametrized tests - [x] **AC-2**: Existing resolver behavior unchanged for single-class files and Fwd/Bwd-suffixed classes — verified by `test_single_class_file_resolves` - [x] **AC-3**: Unit tests cover multi-class resolution scenarios — 21 test cases in `TestResolveOpClass` **Test results:** 72 passed, 0 failed ## Follow-up - #810 — Emit warning on silent `candidates[0]` fallback - #811 — Deduplicate PascalCase resolution loops --------- Co-authored-by: Ibuki 🍃 — a wind born from Claude Opus <Ibuki-wind@users.noreply.github.com> |
|
|
|
f1f9ba1517
|
[Feat][Manifest] Add --check-op flag for spec-only validation override (#780)
## Summary Add a `--check-op <name>` CLI flag to the manifest validator that forces all validation levels (L0-L4) on the specified op, bypassing the `status: spec-only` gate. Default (unflagged) behavior is unchanged. Closes #779 ## Test plan | AC | Description | Status | |----|-------------|--------| | AC-1 | `python scripts/validate_manifest.py --check-op softmax_fwd` runs L0-L4 on `softmax_fwd` even when `status: spec-only` | pass | | AC-2 | `python scripts/validate_manifest.py` (without flag) behavior unchanged -- spec-only ops still skip L1-L4 | pass | | AC-3 | Unit test covers both behaviors (with/without flag, nonexistent op, single-op scoping, variant_of regression) | pass | | AC-4 | Modified files pass existing tests (45/45 passed) | pass | ## Validation - 45 tests passed, 0 failed - Pre-commit hooks pass (ruff, codespell, mdformat, gitleaks) - Files changed: `scripts/validate_manifest.py`, `tests/test_validate_manifest.py` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
ce6da052ae
|
[Chore][Manifest] Strict L1 signature: check __init__() + forward() param union (#768)
## Summary Make L1 validator strictly require that all manifest-declared params for `status: implemented` ops appear in the Op's `__init__()` + `forward()` union signature. This ensures manifest declarations stay in sync with actual code interfaces. - Update `check_l1_signature()` to inspect both `__init__` and `forward` param names - Audit and fix all 23 implemented ops that failed strict L1 - Update `docs/manifest.md` L1 description to reflect the stricter rule Closes #765 ## Test plan - [x] **AC-1**: check_l1_signature() checks manifest params against __init__ + forward union, not just forward - [x] **AC-2**: All status: implemented ops pass strict L1 in CI - [x] **AC-3**: docs/manifest.md L1 description updated to reflect strict matching rule - [x] **AC-4**: Modified files pass existing tests **Test results**: 47/47 passed (24 validator + 23 manifest), 0 failed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
89708ee424
|
[Fix][Benchmark] Add serial validation to correct noisy autotune configs (#773)
## Summary
- Parallel warmup (`-n 8`) causes autotuner to select suboptimal configs
due to GPU contention noise, which get cached and reused by serial
benchmarks
- Add a Phase 2 serial validation pass in `warmup_kernel_cache.py` that
re-tunes on a quiet GPU with `.so` cache hits (compilation instant, only
profiling re-runs)
- Add autotune config column to `BenchmarkReport` so nightly reports
show which config was selected
## How it works
| Phase | Compilation cache (`.so`) | Autotune cache (`best_config`) |
|---|---|---|
| Phase 1: parallel warmup (`-n 8`) | Write (parallel, fast) | Write
(noisy — GPU contention) |
| Phase 2: serial validation | Read (cache hit, instant) | Overwrite
(accurate — exclusive GPU) |
`conftest_warmup.py` supports a new `TILEOPS_WARMUP_VALIDATE=1` mode
that patches `AutoTuner._load_result_from_disk` to return `None`,
forcing re-tune while preserving `.so` cache.
## Evidence
Same kernel (`avg_pool2d vision-3x3-s2`) selects different configs under
contention vs serial:
- **Parallel (8 workers)**: `{'block_m': 128, 'block_c': 64, 'threads':
256}`
- **Serial (3/3 runs consistent)**: `{'block_m': 64, 'block_c': 32,
'threads': 256}`
Fixes #772
## Test plan
- [x] Full warmup + validation pass completes on all benchmark files
- [x] Serial validation correctly overwrites noisy autotune cache
- [x] `BenchmarkReport` includes config column for tileops entries
- [x] Verified on H200 with `tileops-runner:latest`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
|
|
cddd14e7d3
|
[CI] Add gpu smoke workflow summary report (#764)
Closes #763 ## Summary - add a JUnit-XML-based GPU smoke report generator for workflow summaries - publish GPU smoke summary and artifacts from the gpu-smoke workflow ## Test plan - [x] `python -m py_compile scripts/gpu_smoke_report.py` - [x] generated a sample report from a minimal JUnit XML fixture ## Additional context - `Gpu-smoke ops number` reports testcase count, per issue clarification. - The workflow still uses existing pytest metadata from `tests/conftest.py`; no test selection policy changed. |
|
|
|
20170a617d
|
[Chore][Testing] Add unit-test policy and test node growth detection script (#761)
## Summary Define a unit-test policy for operator tests and provide a local script (`scripts/test_node_delta.py`) that reports test node count delta for touched files in a PR, making growth visible and justifiable. Closes #760 ## Changes **M1 — Unit-test policy** - Added Unit-Test Policy section to `docs/testing.md` covering: allowed UT purposes, dtype risk class rule, shape coverage rule, manifest separation, growth justification - Added `.claude/rules/testing-budget.md` with concise agent-enforceable version - Verified policy is consistent with existing smoke/full tier system in `tests/conftest.py` **M2 — Growth-detection script** - Created `scripts/test_node_delta.py` that compares test node counts between current branch and main - Added usage instructions to `docs/testing.md` - Added note in `.claude/rules/testing-budget.md` about running script and justifying growth ## Test plan - [x] **AC-1-1**: docs/testing.md contains a Unit-Test Policy section with dtype risk class, shape coverage, manifest separation, and growth justification rules - [x] **AC-1-2**: .claude/rules/testing-budget.md exists and is loadable by Claude Code - [x] **AC-1-3**: Policy does not contradict existing tier enforcement in tests/conftest.py - [x] **AC-2-1**: Script runs successfully on the current repo and reports node counts for at least 3 existing test files - [x] **AC-2-2**: Script correctly detects delta when a test file has more nodes than its main branch version - [x] **AC-2-3**: Script exits 0 regardless of delta (non-blocking) - [x] **AC-2-4**: Script handles new test files (no main branch version) without error - [x] **AC-2-5**: docs/testing.md documents how to run the script 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
6c0d687439
|
[Chore][Manifest] Add manifest validator with CI integration (#745)
Closes #738 ## Summary - Add `scripts/validate_manifest.py` implementing manifest validation for schema, `Op.forward()` signature consistency, `shape_rules` syntax, dtype conformance, and benchmark manifest usage - Integrate the validator into the preflight workflow with a dedicated `validate-manifest` job - Add MoE manifest entries and migrate the MoE benchmarks in this PR to manifest-driven workloads via `load_workloads()` and `eval_roofline()` - Make benchmark strictness explicit with `source.bench_manifest_driven: true` for migrated entries, while leaving legacy non-migrated benchmarks as warnings - Clarify in `docs/manifest.md` that `workloads` are for nightly/performance benchmark coverage only, not unit-test coverage - Add merge-ready validator coverage in `tests/test_validate_manifest.py` - Add a developer-agent rule in `.claude/rules/manifest-validator.md` forbidding validator edits as a way to bypass manifest constraints ## Test plan - [x] pre-commit passed (all hooks green) - [x] `pytest -q tests/test_validate_manifest.py` -- 20 passed - [x] `pytest -q tests/ops/test_moe_permute.py` -- 9 passed - [x] `python scripts/validate_manifest.py` passes on current codebase (exit 0) ## Structural Readiness SKIP -- primary deliverable is infrastructure (manifest validator + CI integration), not a new kernel/op. ## Additional context **Validation checks:** - **Schema**: YAML structure, required fields, and field types - **Signature**: `Op.forward()` parameters must match manifest inputs, plus any accepted manifest-declared runtime params, in order - **Shape**: `shape_rules` must parse as valid Python expressions - **Dtype**: manifest dtype strings and workload dtypes must be recognized - **Benchmark**: migrated benchmark files must import and call `load_workloads` / `eval_roofline` with the correct op name **Current enforcement model:** - `status: spec-only` entries receive schema-only validation - L4 benchmark enforcement is hard-fail only for entries that explicitly opt in with `source.bench_manifest_driven: true` - Legacy benchmark files that have not yet migrated remain warnings, not CI failures --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
857d875d19
|
[Bench] Add FA3/FlashInfer baselines across all attention benchmarks (#734)
## Summary - **Fix FA3 import**: `bench_mha.py` used `import flash_attn_interface` (not installed), silently falling back to `torch-sdpa`. Fixed to `from flash_attn import flash_attn_func`. - **Add FA3 baselines**: `bench_mha_decode`, `bench_gqa_decode`, `bench_mha_decode_paged`, `bench_gqa_decode_paged` — previously only had `torch-sdpa`/`torch-ref`. - **Add FlashInfer baselines**: All 8 attention benchmark files now include FlashInfer baselines using batched APIs (`BatchPrefillWithRaggedKVCacheWrapper` / `BatchDecodeWithPagedKVCacheWrapper`). - **Fairness fix**: Replaced per-batch `single_prefill_with_kv_cache` loops with batched wrappers. CUPTI sums all kernel durations — per-batch loops launch B separate kernels that can't exploit cross-batch parallelism. - **bf16 fix**: FlashInfer `wrapper.plan()` defaults `q_data_type='float16'`. Added explicit `q_data_type=q.dtype` to all plan() calls. Note: the decode wrapper's `data_type` param does NOT propagate to `q_data_type`. - **Multi-baseline report**: `conftest.py` writes all baselines to JUnit XML with tag-prefixed properties. `nightly_report.py` parses and displays multiple baselines per config. ## Scope | File | Changes | |:-----|:--------| | `bench_mha.py` | Fix FA3 import + add FlashInfer fwd | | `bench_mha_decode.py` | Add FA3 + FlashInfer | | `bench_gqa.py` | FlashInfer: per-batch loop to batched API | | `bench_gqa_decode.py` | Add FA3 + FlashInfer | | `bench_mha_decode_paged.py` | Add FA3 (page>=256) + FlashInfer paged | | `bench_gqa_decode_paged.py` | Add FA3 (page>=256) + FlashInfer paged | | `bench_gqa_sliding_window_fwd.py` | Add FlashInfer (wr<0 only) | | `bench_gqa_sliding_window_varlen_fwd.py` | Add FlashInfer ragged (wr<0 only) | | `conftest.py` | Multi-baseline JUnit XML properties | | `nightly_report.py` | Multi-baseline parsing, display, alerts | ## Skipped (expected) - **bwd**: FlashInfer has no backward API - **window_right >= 0**: FlashInfer only supports `window_left` - **FA3 paged with page_size < 256**: FA3 requires page_block_size multiple of 256 ## Test plan - [x] 36/36 benchmarks passed in `tileops-runner:latest` on H200 (GPU1, 1830 MHz locked) - [x] bf16 configs verified: FlashInfer data present for all non-bwd, non-window_right configs - [x] Multi-baseline XML properties correctly written (fa3 + flashinfer tags with latency/ratio) - [ ] Nightly CI should now report fa3 and flashinfer baselines across all attention ops --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
8bccfa30e7
|
[CI] Fix warmup OOM, enable autotuner caching, skip baselines (#685)
## Summary Three fixes to the nightly warmup phase: 1. **Fix GPU OOM causing warmup to hang** — pytest-xdist workers accumulated GPU memory over their lifetime; idle workers held ~140GB while the last worker starved and hung at 98%. Added `gc.collect()` + `torch.cuda.empty_cache()` after each test. 2. **Enable autotuner result caching** — Removed `TILELANG_AUTO_TUNING_DISABLE_CACHE=1` and the `do_bench` mock patch. Warmup now runs real GPU profiling and saves `best_config.json` to disk. Subsequent warmup and benchmark runs hit the autotuner cache (~2ms per kernel instead of minutes). 3. **Skip baseline profiling during warmup** — Patched `BenchmarkBase.profile` to return dummy results for non-Op functors (torch-cublas, FA3, etc.). Baseline measurements serve no purpose during warmup. Also bumped default pytest-xdist workers from 8 to 16. **Result**: warmup dropped from 38min (hanging at 98%) to 9min (912/912 passed). ## Changes - `scripts/warmup_kernel_cache.py` — Remove `TILELANG_AUTO_TUNING_DISABLE_CACHE=1`, default `-n 16` - `scripts/conftest_warmup.py` — Remove `do_bench` mock, add GPU memory cleanup hook, add baseline skip - `.github/workflows/nightly.yml` — Bump warmup to `-n 16` ## Test plan - [x] Warmup completes without OOM (912/912 passed, GPU memory stable at ~20-30GB) - [x] Warmup time: 38min (hanging) → 9min with caches warm - [x] Autotuner cache populated (best_config.json written to disk) - [ ] Verify all 4 nightly phases complete via `workflow_dispatch` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
3471d9adfd
|
[Fix][CI] Install external baseline libraries in nightly benchmarks (#673)
## Summary Closes #670. Nightly benchmarks were missing external baseline libraries (flash-attn, fla, vllm, sgl-kernel, native_sparse_attention), causing benchmarks to fall back to slow PyTorch reference implementations and inflating speedup numbers. - Add `bench` extras group in `pyproject.toml` with pinned versions (flash-attn 2.8.3, flash-linear-attention 0.4.2, vllm 0.18.0, sgl-kernel 0.3.21) - Relax torch upper bound from `<2.10.0` to `<2.11.0` for vllm 0.18.0 compatibility (tilelang 0.1.8 verified compatible with torch 2.10.0) - Add "Install benchmark baseline libraries" step in nightly Phase 1 (cache-warmup) and Phase 2 (benchmark), always executed regardless of `VENV_REUSED` — pip is idempotent and completes in seconds when versions are unchanged - Exclude `bench` group from `dep_hash.py` so bench version bumps do not trigger full venv rebuilds or invalidate kernel compile caches - Pin `native_sparse_attention` to commit `bd67af59` (not on PyPI); gracefully skip on GitHub network errors - Add `install-bench` Makefile target for local development ## Test plan - [x] `python scripts/dep_hash.py` produces identical hash before and after adding `bench` group (confirms exclusion works) - [x] `pip install --dry-run` resolves all bench deps + core deps without conflicts (torch 2.10.0, tilelang 0.1.8, vllm 0.18.0, flash-attn 2.8.3, flash-linear-attention 0.4.2, sgl-kernel 0.3.21) - [ ] Next nightly run should show benchmarks using real baselines (fa3, fla, etc.) instead of torch fallbacks 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
|
|
|
a49f6aeed2
|
[Fix][Bench] Fix 11 nightly benchmark failures and report omission (#635)
## Summary - **Backward baselines fail under `no_grad()`**: `BenchmarkBase.profile()` wraps calls in `torch.no_grad()`, breaking autograd-based torch SDPA backward baselines when FA3 is unavailable. Fixed by adding `@torch.enable_grad()` to baseline functions in `bench_engram_bwd`, `bench_gqa`, and `bench_mha`. - **mha_decode `result_idx` OOB**: When autotune selects `num_split>1` but `seqlen_kv` is too small, the runtime fallback to `num_split=1` recompiles `mha_decode_no_split` (5 params) with `out_idx=[7]` (resolved from the 8-param split function). Fixed by guarding the split kernel's `logsum` division against zero (empty splits) and removing the runtime `num_split` fallback. - **Nightly report silently drops benchmark failures**: `aggregate_bench_results()` filters `outcome != "passed"`, so failed benchmarks never appear. Added `collect_bench_failures()`, a "Benchmark Failures" section, summary row, and health indicator integration. Closes #634 ## Test plan - [x] `bench_engram_bwd` — all 4 cases pass locally (no FA3) - [x] `bench_gqa::test_gqa_bwd_bench` — all 3 cases pass locally (no FA3) - [x] `bench_mha::test_mha_bwd_bench` — all 3 cases pass locally (no FA3) - [x] `bench_mha_decode::test_mha_decode_bench[short-kv-tail]` — passes locally - [x] `test_mha_decode` unit tests — all 3 cases pass (including `seqlen_kv=5`) - [x] Nightly report shows benchmark failures in synthetic test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
51fb1ef0bb
|
[Fix][Bench] Fix nightly report: broken summary, formatting, missing baselines (#626)
## Summary Closes #625 Four fixes for the nightly report pipeline: ### A. Remove broken benchmark step summary The Phase 2 bash XML parser failed on single-line XML (`<testsuites>` and `<testsuite>` on same line → regex never matched → all counters 0). Removed entirely since Phase 3 nightly report already covers this. ### B. Improve nightly report aesthetics - Emoji status indicators, color-coded ratio circles - `<details>` collapsible sections for long tables (120+ correctness, 600+ benchmark rows) - Right-aligned numeric columns, bold Op names, `code` module paths - Emoji unicode centralized as module-level constants ### C. Annotate strategy benchmark entries 54 `test_fused_gated_strategy_bench` entries had blank Via column. Added `tileops_variant` property in conftest → report shows `strategy: direct` / `strategy: explicit_parallel`. ### D. Add missing baseline to `bench_engram_bwd.py` 4 entries had no baseline at all. Added `test.ref_program` with `tag="torch"`. ## Report Preview **Header & Summary (all healthy):** ``` # ✅ TileOPs Nightly Report > **2026-03-23 04:30** | `14e2cbc` | NVIDIA H200 | | | |---|---| | **Correctness** | ✅ (788/788 tests across 123 ops) | | **Benchmarked Ops** | 129 | | **Regressions** (vs 14-day best) | ✅ None | | **Baseline Alerts** (< 80%) | ⚠️ 58 | ``` **Header & Summary (with failures):** ``` # ❌ TileOPs Nightly Report | **Correctness** | ❌ 1 failed (791/793 tests across 124 ops) | | **Regressions** (vs 14-day best) | ⚠️ 1 | | **Improvements** (vs 14-day best) | 🎉 1 | ``` **Regression table:** ``` ## ⚠️ Performance Regressions (vs 14-day best) | Op | Config | Best (ms) | Current (ms) | Delta | TFLOPS | |:---|:-------|----------:|-----------:|------:|-------:| | **SoftmaxOp** | test_softmax_bench[mainstream-fp16] | 0.0050 | 0.0161 | +222.0% | 1.04 | ``` **Strategy annotation in Via column:** ``` | | gelu_and_mul_strategy | ...-direct] | 0.0173 | ... | strategy: direct | - | | | gelu_and_mul_strategy | ...-explicit_parallel] | 0.0109 | ... | strategy: explicit_parallel | - | ``` ## Test plan - [ ] Verify nightly report renders correctly on next nightly run - [ ] Verify engram bwd baseline rows appear in bench_results.xml - [ ] Verify strategy entries show `strategy: xxx` in Via column 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
14e2cbceae
|
[CI] Add Op-level nightly report with correctness, performance, and regression tracking (#620)
## Summary
- Inject Op metadata (class name, module path, error metrics, perf data)
into pytest JUnit XML `<property>` elements
- Add `scripts/nightly_report.py` to generate an Op-granularity markdown
report from JUnit XML
- Integrate report generation into nightly CI workflow with
`perf_history.json` artifact for 14-day regression tracking
## Changes
### Data collection (pytest side)
- **`tests/test_base.py`**: `check()` now records
`op.__class__.__name__`, `op.__class__.__module__`, `max_abs_err` via
logging + thread-local
- **`tests/conftest.py`**: `pytest_runtest_call` hookwrapper writes Op
info to `item.user_properties` → JUnit XML `<property>`
- **`benchmarks/benchmark.py`**: `record()` accepts Op objects
(backward-compatible), accumulates tileops + baseline data
- **`benchmarks/conftest.py`**: hookwrapper writes perf data + baseline
ratio to `item.user_properties`
- **`benchmarks/ops/bench_*.py`** (52 files): `record("name", ...)` →
`record(op, ...)`
### Report generation
- **`scripts/nightly_report.py`**: Parses JUnit XML, aggregates by Op,
detects regressions vs 14-day best, baseline alerts (ratio < 80%),
generates markdown
- **`.github/workflows/nightly.yml`**: Downloads artifacts, runs report
script, posts to `$GITHUB_STEP_SUMMARY`, manages `perf_history.json`
artifact
### JUnit XML output format
```xml
<!-- Correctness test -->
<testcase name="test_gqa_fwd[smoke-fwd-fp16]">
<property name="op" value="GroupQueryAttentionFwdOp"/>
<property name="op_module" value="tileops.ops.gqa"/>
<property name="max_abs_err" value="2.44e-04"/>
</testcase>
<!-- Benchmark -->
<testcase name="test_gqa_fwd_bench[throughput-fp16]">
<property name="op" value="GroupQueryAttentionFwdOp"/>
<property name="tileops_latency_ms" value="0.0104"/>
<property name="tileops_tflops" value="206.26"/>
<property name="baseline_tag" value="FA3"/>
<property name="baseline_latency_ms" value="0.35"/>
<property name="baseline_ratio" value="0.83"/>
</testcase>
```
## Test plan
- [x] `pytest tests/ops/test_gqa.py -m smoke --junit-xml=test_out.xml` →
XML contains `op`, `op_module`, `max_abs_err` properties
- [x] `pytest benchmarks/ops/bench_gqa.py -k prefill
--junit-xml=bench_out.xml` → XML contains perf properties
- [x] `python scripts/nightly_report.py --test-xml test_out.xml
--bench-xml bench_out.xml --output report.md` → generates valid markdown
report
- [x] All pre-commit hooks pass (ruff, codespell, etc.)
- [ ] Nightly workflow dispatch to verify CI integration
Closes #618
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
|
|
27c6688808
|
[BugFix][CI] Fix warmup error handling, monkeypatch cleanup, and portable cache paths (#548)
## Summary
- Fix warmup script to propagate infrastructure errors (pytest exit
codes 2-5) while still tolerating test failures (exit code 1)
- Restore original `ThreadPoolExecutor` in `pytest_unconfigure` to
prevent global state leaks
- Replace all hardcoded `/home/ci-runner/` paths with `${HOME}/` for
runner portability
Closes #545
Closes #546
## Changes
### 1. Warmup exit code handling (#545)
```diff
- sys.exit(0) # unconditional
+ if exit_code in (0, 1):
+ sys.exit(0) # tests passed or failed — compilation succeeded
+ else:
+ sys.exit(exit_code) # infrastructure error — surface it
```
| Exit Code | Meaning | Before | After |
|-----------|---------|--------|-------|
| 0 | All passed | exit 0 | exit 0 |
| 1 | Test failures | exit 0 | exit 0 (compilation still worked) |
| 2 | Interrupted / bad args | exit 0 (swallowed) | **exit 2** |
| 3 | Internal error | exit 0 (swallowed) | **exit 3** |
| 4 | Usage error | exit 0 (swallowed) | **exit 4** |
| 5 | No tests collected | exit 0 (swallowed) | **exit 5** |
### 2. ThreadPoolExecutor cleanup (#545)
```diff
def pytest_unconfigure(config):
patcher = getattr(config, "_warmup_patcher", None)
if patcher is not None:
patcher.stop()
+
+ orig_pool = getattr(config, "_warmup_orig_pool", None)
+ if orig_pool is not None:
+ concurrent.futures.ThreadPoolExecutor = orig_pool
```
### 3. Portable cache paths (#546)
```diff
- TILELANG_CACHE_DIR="/home/ci-runner/.tilelang/cache"
+ TILELANG_CACHE_DIR="${HOME}/.tilelang/cache"
```
Applied to all 4 jobs (cache-warmup, benchmark, op_test, packaging). 14
occurrences replaced.
## Test plan
- [ ] Verify warmup exits non-zero when pytest encounters infrastructure
errors
- [ ] Verify warmup exits 0 when tests fail but compilation completed
- [ ] Verify no hardcoded `/home/ci-runner/` paths remain
- [ ] Verify cache paths resolve correctly on CI runner
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
|
|
7b94393188
|
[CI] Separate kernel compilation from profiling and parallelize compilation in nightly (#542)
## Summary - Add a `cache-warmup` job to the nightly workflow that pre-compiles all benchmark kernel variants before the benchmark job runs, separating compilation (CPU-bound) from profiling (GPU-bound) - Fix venv cache hash to only include dependency fields from `pyproject.toml`, preventing unnecessary rebuilds from linting/style config changes - Restructure nightly job flow: `cache-warmup → benchmark → op_test → packaging` ## Background In [nightly run #23161046010](https://github.com/tile-ai/TileOPs/actions/runs/23161046010), the benchmark job hit the 90-minute timeout at only 48% progress. Analysis showed ~80-90% of the time was spent on kernel compilation (`g++`/`cc1plus` still running at cancellation), not GPU profiling. Root causes: 1. **Venv prefix rename** in #530 caused `VENV_REUSED=false` → pip reinstalled tilelang → `libtilelang.so` mtime changed → **all kernel caches invalidated** (tilelang cache keys include `.so` mtime) 2. **`tune=True` benchmarks** (GEMM 288 configs, MHA, etc.) compile hundreds of kernel variants through TIR → CUDA → nvcc/g++ pipeline, all serially during pytest execution ## Changes ### 1. Kernel cache warmup (`scripts/warmup_kernel_cache.py`) - Monkeypatches `tilelang.profiler.do_bench` to return dummy value → compilation happens normally, GPU profiling skipped - Sets `TILELANG_AUTO_TUNING_DISABLE_CACHE=1` → dummy results not persisted to autotuner cache - Two levels of parallelism: - **pytest-xdist** (`-n 8`): 8 worker processes running different benchmark test cases simultaneously - **ThreadPoolExecutor** (`--max-workers 64`): within each `tune=True` op, autotuner compiles up to 64 config variants in parallel - **Zero maintenance**: new ops with benchmarks are automatically discovered and pre-compiled - Supports `--shard`/`--total-shards` for future parallelization across runners ### 2. Venv hash fix (Closes #540) ```diff - sha256sum pyproject.toml + python3 scripts/dep_hash.py # hashes only dependencies, optional-deps, requires-python, build-requires ``` Applied to all 4 jobs (cache-warmup, benchmark, op_test, packaging). Changes to ruff rules, codespell config, pytest markers etc. no longer trigger venv rebuild. ### 3. Job dependency restructure & timeout adjustments ``` cache-warmup (60 min) → benchmark (120 min) → op_test (180 min) → packaging ``` GPU jobs (benchmark, op_test) remain serial for measurement accuracy. Compilation cost is moved to the warmup phase. ## Local benchmark results (H200) Tested on a clean tilelang cache to simulate a worst-case cold start: | Phase | Duration | |-------|----------| | Warmup (n=8, --max-workers 64, cold cache) | 24 min | | Benchmark (warm cache, first run) | 73 min | | **Total** | **97 min** | ### Comparison with the previous approach | | Before | After (this PR) | |---|---|---| | Compilation | Serial, interleaved with profiling. 90 min timeout at 48% — estimated **3h+** to complete | Warmup job: 8 pytest workers × 64 compile threads, **24 min** | | Benchmark | Blocked behind compilation, never finished | All kernels pre-compiled, **73 min** (first run) | | **Total** | **> 3h (timeout, incomplete)** | **97 min (complete)** | ### Expected time in subsequent nightly runs (steady state) Both the kernel cache and autotuner result cache are stored in persistent directories on the self-hosted runner (`TILELANG_CACHE_DIR`). As long as the venv is not rebuilt (i.e., dependencies unchanged), these caches persist across nightly runs: | Phase | First run (cold cache) | Subsequent runs (warm cache) | |-------|----------------------|---------------------------| | Warmup | 24 min (full compilation) | **~2-3 min** (all cache hits, no actual compilation) | | Benchmark | 73 min (autotuner profiles all configs) | **~30-40 min** (autotuner cache hit → skip profiling 288 configs per op, only benchmark the best config) | | **Total** | **97 min** | **~35-45 min** | ### Additional notes - **Deepseek kernel issues**: Several Deepseek-related kernels currently have compilation or runtime issues (6 consecutive failures at ~48% progress). Once fixed, the time spent on their failed compilation/retry will be recovered. - **Warmup failures are non-blocking**: The warmup job uses `continue-on-error: true` and always exits 0. Any kernel that fails to compile during warmup will simply be compiled on-demand during the benchmark job (same behavior as before this PR). ## Test plan - [x] Verify warmup script compiles kernels and populates cache (local H200) - [x] Verify benchmark completes with warm cache (local H200, 73 min) - [x] Verify n=8 xdist parallelism works (24 min vs n=4 at 32 min) - [ ] Verify nightly completes within timeout on CI runner - [ ] Verify venv hash is stable across non-dependency pyproject.toml changes Closes #540 Closes #541 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |