Skip to content

chore: repository cleanup and tilebench package layout - #313

Merged
Jokeren merged 25 commits into
mainfrom
bowen/infra/repo-cleanup
Sep 20, 2026
Merged

Jokeren merged 25 commits into
mainfrom
bowen/infra/repo-cleanup

Conversation

@bowencui123

@bowencui123 bowencui123 commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

The repository-cleanup half of the former #312. The README rewrite is #314, stacked on this branch. Since the split this PR has also gained the artifact-storage work (items 2 and 4 below).

What this does

  1. Task descriptions get a directory of their own. benchmarks/problems/current/<op>_current.md is a prompt input of the LLM pipeline, not part of an operator's benchmark definition, so the 45 files move to tilebench/problems/, a data directory beside benchmarks/, core/ and data/, reached through PROBLEMS_ROOT in tilebench/paths.py; the 16 unused problems/original files go. Rebuilt iter-0 prompts keep a description block identical to the archived campaign prompts.
  2. Raw logs leave version control. The raw logs were force-added despite .gitignore. They are generated artifacts: results/<gpu>/logs/ is Git-ignored, a benchmark run writes its logs there, and that is all it does. run_bench.py performs no Git operation and has no archive hook or flag. The paper's frozen raw logs are preserved on archive/raw-logs-2026-09-18 (item 10).
  3. data/peak_performance. .gitkeep dropped, check_peak_specs.py moved in from the repo root, and it now reads measured peaks from B200.json instead of hardcoding 8000 GB/s.
  4. LLM generation artifacts leave the main source tree. The generation and evaluation pipeline remains version-controlled under tilebench/llm/, while generated trajectories under tilebench/benchmarks/llm_generated/ are now Git-ignored experimental artifacts. The complete frozen AACL 2026 campaign (45 operators × {gpt-5.5, claude-opus-4-7}, 732 iterations) is preserved on the archive branch and published as a downloadable artifact, with scripts/fetch_artifacts.py restoring it to the expected runtime path. artifacts/manifest.json declares downloadable artifacts (URL, SHA256, archive name, provided directories); scripts/package_artifacts.py builds the reproducible tarball. LLM_GENERATED_ROOT and generate.py are unchanged: same runtime path, different storage policy. The branch history still contains the commits that restored the campaign from exp/llm_and_analysis before it was untracked, so squash-merging keeps those 6,140 files out of main's history.
  5. skills/ is tracked. The Triton and cuTile API references were read from the untracked .claude/skills/, so a fresh clone built prompts without them.
  6. Sources consolidate under one package. core/, data/, benchmarks/, tools/llm_codegen/ and tilebench_run/ become tilebench/{core,data,benchmarks,llm,profiling}/, with the task descriptions in tilebench/problems/; generated NCU reports move to outputs/, and the profiling command-line tools to scripts/profiling/ (item 13). New tilebench/paths.py resolves every package resource from __file__, replacing relative paths, parent-count repo-root guesses and ten scripts that embedded one machine's absolute checkout path. scripts/ put the repo root on sys.path themselves, so PYTHONPATH=. is no longer needed and they work from any directory. A minimal pyproject.toml makes pip install -e . work.
  7. results/ tracks only CSVs. Figures, aggregates and runtime_summary.md are regenerated by visualize.py and aggregate_results.py, so they leave tracking; .gitignore had been ignoring the summary CSVs, the one thing that should be versioned. With item 9 the tracked set is results/<hardware>/csv/**/*.csv for any hardware label, and aggregate CSVs stay ignored.
  8. Missing kernel-count metadata is an error. The NCU harness validates every capture against probed kernel counts and names. A missing file now raises, naming the command that produces it, instead of silently assuming one kernel per launch. Where that metadata lives is item 12.
  9. Results are scoped by hardware. TileBench will hold results for several GPUs (B200 today; GH200 and an AMD part to follow), and a global results/{csv,logs,figures,aggregate,runs} lets the same operator overwrite itself across machines. The layout is now results/<gpu>/{csv,logs,figures,aggregate,runs}/, built only through helpers in tilebench/paths.py (results_root, results_csv_dir, ...). hardware_label() accepts any single safe path component and rejects traversal; there is no device list, so a new GPU needs a label, not a patch. The 90 committed CSVs are B200 data and move to results/B200/csv/ as a pure rename. run_bench.py, run_bench_all.py, visualize.py, plot_sweep_max.py, aggregate_results.py and ncu_catalogue.py take --gpu, required and with no default, so a run on another machine cannot land in B200's namespace. The PyTorch, Triton, cuTile and TileLang columns of a namespace were measured on that GPU, and the TileLang merge is confined to the CSV of the current --gpu. NKI keeps its original organisation inside the new layout: run_bench.py --gpu B200 --tile-language nki merges torch_nki_ms, nki_ms and speedup_nki into results/B200/csv/, exactly as on main, and its logs and Neuron profiles go to results/B200/logs/ (nki_profiles/, nki_neff_manifest.jsonl). --gpu names the campaign, not the device NKI ran on. These are cross-hardware measurements: speedup_nki = torch_nki_ms / nki_ms, never torch_ms / nki_ms, and NKI latencies get no B200 drift scaling.
  10. The archive branch is this branch plus archive-only material, and owns its own tooling. archive/raw-logs-2026-09-18 absorbs this branch by merge and adds what the public tree does not keep: the paper's 272 raw logs, under results/B200/logs/ (a4245d86 moved them there from the flat results/logs/), both LLM snapshots, the B200 NCU metadata, the retired profiling files of item 13, and the archive tooling. That tooling, a maintenance script and its tests, lives on the archive branch only: backing results up targets one dated branch and is a maintainer task, not a feature of the framework, so this PR ships none of it. There the script carries itself and its tests forward, so an archive run on a newer main cannot delete them.
  11. Raw JSON is collision-free. results/<gpu>/logs/ held one <op>_results.json and one <op>_autotune.json, so a default run, an autotune run, a TileLang-only run and an NKI run of one operator overwrote each other. Default names are now <op>_<mode>_<backends>.json, for example mul2_default_triton-cutile.json, mul2_autotune_triton-cutile.json, mul2_default_tilelang.json, mul2_default_nki.json, built only by timing_log_path / autotune_log_path. The new tilebench/backends.py owns the backend names, their canonical order and the --tile-language parser, so cutile,triton and triton,cutile are one tag. Explicit --output / --autotune-log still win. Readers name the run they want: visualize.py takes --mode and --tile-language, and ncu_catalogue.py reads exactly <op>_autotune_<backends>.json (default triton,cutile, both required). Nothing is chosen by glob or modification time, and there is no latest.json. With one file per selection the autotune-log merge is removed: it existed to stop a TileLang run clobbering the Triton/cuTile winners, and would now refuse a rerun with different cases. run_bench.py is the only owner of these names: nothing renames or copies results afterwards.
  12. Profiling metadata and NCU reports are scoped by hardware, and are generated data. Autotune winners, kernel launch counts and kernel names are measured on one GPU, so the single global pair becomes one directory per GPU. They are written by scripts/profiling/ncu_catalogue.py and scripts/profiling/probe_kernel_count.py and read only by the NCU tools, never by a benchmark code path, so they are experiment data rather than a package resource: they live under the Git-ignored outputs/profiling/<gpu>/{ncu_catalogue,kernel_counts}.json, next to the reports in outputs/ncu/<gpu>/, and nothing is committed for any GPU. The B200 files are kept on the archive branch. NCU_CATALOGUE and KERNEL_COUNTS are removed in favour of ncu_catalogue_path(gpu), kernel_counts_path(gpu) and ncu_output_dir(gpu), which use the same label validation as results_root. ncu_catalogue, probe_kernel_count, ncu_one, ncu_driver, ncu_writeup, hf_upload and plot_sweep_max take --gpu, required and without a default. A GPU without metadata is an error that names the command to produce it; another GPU's files are never used. hf_upload uploads under ncu_report_main/<gpu>/, so it cannot overwrite the released B200 reports, which were not touched. plot_sweep_max.py no longer needs this metadata: the sweep-max case comes from each operator's config.yaml through the rule the catalogue uses, so the README figure is reproducible from the configs and the committed CSVs alone.
  13. tilebench/profiling/ is a library; programs live under scripts/. A file is not package source because it ends in .py, so the 17 Python files there were classified by what they do and who imports them. Library, stays (3): __init__.py; ncu_kernel_select.py, kernel selection, capture validation and metadata loading, imported by four tools; ncu_catalogue.py, sweep-max cases and catalogue entries, imported by plot_sweep_max.py, now without its argparse main and sys.path hack. Command-line tools, moved (8): scripts/aggregate_results.py, which aggregates result CSVs and so sits with visualize.py; scripts/profiling/{ncu_catalogue,ncu_driver,ncu_one,ncu_writeup,probe_kernel_count,hf_upload}.py, none of which had an importer outside the tests; and scripts/profiling/ncu_generic_harness.py, which is never imported because it is the process NCU profiles. They put the repository root on sys.path like the other scripts and run from any directory without PYTHONPATH; the drivers locate the harness next to themselves, and the report path rule they duplicated is now tilebench.paths.ncu_report_path. Campaign-specific, removed and kept on the archive (12): five cluster launch scripts (.sh, .sbatch), probe_hung_cfgs.py and six ncu_harness_*.py: hard-coded operators, cases, configs and cluster paths, code that runs at import, the methodology the generic harness replaced, and no reference from any driver, test or document. They are not moved to scripts/. The tilebench.profiling package-data entry is dropped.

Behaviour

Measurement behaviour is unchanged: operator code, case grids, dtypes, tolerances, autotune spaces, timing protocol, CSV values and the peak-performance specs are untouched. For the package move, 6429 of 6507 moved files are byte-identical and the 78 modified carry import/path edits only.

Item 9 deliberately changes the command line:

  • --gpu <label> is required by every result-producing script. run_bench.py prints the namespace next to the detected device and warns when the label does not appear in the device name.
  • The default backend set, and all, is triton, cutile, tilelang. It used to include nki, which a GPU host only ever recorded as nan. NKI still runs whenever it is named.
  • NKI profiling artifacts move from results/logs/nki_profiles/ to results/<gpu>/logs/nki_profiles/. The engine takes a logs_dir and hands the paths to the orchestrator, which no longer has a default location. NKI execution, timing and the CSV merge are unchanged, and its logs are archived with the rest of results/<gpu>/logs/.
  • Default raw JSON names change to <op>_<mode>_<backends>.json (item 11). visualize.py gains --mode and --tile-language to name the run it reads, and ncu_catalogue.py gains --tile-language. The archived paper logs keep their original file names.
  • The NCU tools require --gpu (item 12), and hf_upload.py uploads under a per-GPU prefix.
  • Profiling tools are run as python scripts/profiling/<tool>.py, and the aggregator as python scripts/aggregate_results.py, instead of python -m tilebench.profiling.<tool> (item 13). Cluster launch scripts and campaign-specific harnesses are gone from the public tree.
  • run_bench.py writes results and performs no Git operation. Backing results up is not part of a run, and there is no flag for it.
  • plot_sweep_max.py writes to results/<gpu>/figures/ unless --output is given, so plotting another GPU cannot overwrite the README figure. Its title names the --gpu label instead of a hard-coded "NVIDIA B200".

Verification

  • pytest: 166 passed, 1 skipped. compileall clean. Tests that only fingerprinted one migration (tests/data/b200_csv.sha256, the B200 metadata hashes) or exercised removed launch scripts were deleted, and the 19 archive-script tests moved to the archive branch with the script, where they pass. The CSV schema test, the namespace and isolation tests and the NKI merge tests stay.
  • The B200 CSV move is byte-identical: 90 files before and after, SHA256 recorded before git mv and matched after, all 90 git blobs unchanged, 90 renames at 100% similarity with 0 lines added or removed. 56 of the files use CRLF and were not normalised; the move was committed with --no-verify so the whitespace hooks could not touch the data. The hashes were checked at migration time and are recorded here; no fingerprint file is kept in the repository.
  • The committed CSVs have the 10 frozen columns and no gpu/device/hardware column: the directory names the hardware. The only extension the guard accepts is the three NKI columns, checked row by row against torch_nki_ms / nki_ms.
  • A real run on a B200 with default flags, run_bench.py --gpu SMOKE --operator mul2 --tile-language triton --case-indices 0, started from /tmp, wrote only under results/SMOKE/, printed the namespace with the detected device, raised the label warning, and left the local archive ref untouched. visualize.py --gpu SMOKE read and wrote inside the same namespace. The 90 B200 CSVs were re-hashed afterwards and were unchanged. Omitting --gpu is rejected before anything runs. A test stubs the engine and asserts that run_bench.py itself starts no process.
  • The new tests were mutation-checked: restoring the old .gitignore rules, computing speedup_nki from the B200 torch_ms, or drift-scaling nki_ms each makes the corresponding test fail.
  • .gitignore was exercised with git check-ignore: results/{B200,GH200,MI300X}/csv/x.csv are trackable; logs, figures, runs, aggregate/x.csv, non-CSV files under csv/ and the legacy results/logs/ are ignored; no hardware name appears in the rules.
  • The published LLM artifact contains 6,140 files.
  • A real download, SHA256 verification, extraction, and tree comparison reproduced the local frozen campaign exactly.
  • tilebench/benchmarks/llm_generated/ remains the canonical runtime output path but is no longer tracked on the main source branch: git ls-files is empty there, git check-ignore matches, and the pipeline still creates the directory on demand.
  • The archive branch (tip 734bbd56, no force push) absorbs this branch by git merge --no-ff, five times so far. First merge (5df592a7): 272 rename/delete conflicts, the already-migrated B200 logs, kept; the merge had silently deleted the 284-file legacy LLM snapshot, restored. The later merges had no conflicts; whatever this branch had dropped, seven campaign-specific profiling files and then the three archive tooling files, was restored byte for byte from the last commits that had them (3ba7c669, b6cc83cb). Now the source part of the archive tree is identical to this branch (429 files, 0 differing, 0 missing) and the archive adds 6,713 files: 6,140 LLM, 284 legacy LLM, 272 raw logs, 2 B200 metadata files, 12 retired profiling files and 3 archive tooling files; 429 + 6,713 = 7,142, the size of the archive tree. No previously archived file is missing or changed, and the four data subtrees keep their tree hashes. The archive branch's own copy of its script, run from a clean checkout of this branch, is a no-op, and its 19 tests pass in a worktree of that branch.
  • fetch_artifacts.py is tested for checksum mismatch, refusal to overwrite without --force, and rejection of traversal, links and members outside the declared directories.
  • 45 operators discovered; all 45 config.yaml parse to identical hashes; all 180 impl_* modules import.
  • The 45 rebuilt LLM prompts are byte-identical to the pre-refactor build, and were re-hashed before and after the task descriptions moved to tilebench/problems/ and the pipeline to tilebench/llm/: 45 of 45 unchanged. That move is 55 renames at 100% similarity, committed with --no-verify so the whitespace hooks could not touch prompt inputs. A wheel and an sdist carry the 45 descriptions and the tilebench.llm package, and a test asserts that every operator has a description under PROBLEMS_ROOT.
  • The B200 profiling metadata (ncu_catalogue.json sha256 814bb30d…, kernel_counts.json sha256 cfe0687e…) left the public tree without a content change and is on the archive branch with the same blob ids (6db68b1a, 8d8311bb). Peak-performance loads match their pre-move hashes.
  • Raw JSON: a sequence of B200 default, B200 autotune, B200 TileLang-only, B200 NKI and GH200 default leaves ten distinct files, each byte-identical to what its own run wrote. On a real B200, --tile-language triton and cutile,triton produced mul2_default_triton.json and mul2_default_triton-cutile.json, and visualize.py, started from /tmp, read the second by name.
  • Metadata isolation: building the GH200 catalogue or probing GH200 kernel counts leaves the B200 files byte-identical, and the reverse. The catalogue reads the one named autotune log even when newer decoy logs of other modes and selections sit beside it; adding a fallback to B200 makes a test fail. The sweep-max cases re-derived from config.yaml alone match the former committed catalogue for 45 of 45 operators.
  • Packaging: a wheel and an sdist built from a source copy contain exactly three files under tilebench/profiling/ (__init__.py, ncu_catalogue.py, ncu_kernel_select.py), no scripts/, no harness or command-line tool, no .sh, .sbatch or metadata, and still ship the peak-performance JSON, the 45 operator configs and the prompt files. A test installs the package with pip install --target and asserts the same from outside the repository. Another asserts, by parsing the package, that no module under tilebench/profiling/ has a __main__ guard, imports argparse or runs code at import.
  • The relocated tools were run for real on a B200, started from /tmp with no PYTHONPATH: ncu_catalogue.py and probe_kernel_count.py for a throwaway label, then ncu_one.py under the ncu binary, which executed the relocated generic harness and validated 1/1 launches. Tests start every tool from outside the repository up to argument parsing, and check that both drivers point at the harness next to them; removing a script's sys.path bootstrap, pointing a driver back into the package, or dropping a program into the package each makes a test fail.
  • git grep finds no /projects/kzhou6, no PYTHONPATH=., no _summary.csv consumer, no global metadata constant, no python -m tilebench.profiling command and no reference to a removed launch script or harness outside skills/, which is the frozen prompt input of the paper campaign and is left byte-identical on purpose.
  • Smoke benchmarks on B200 for mul2, vector_add, mean_reduction and matmul_int8, plus one autotuned case; all backends discovered.
  • pip install -e . resolves package data from outside the repo.

Open PRs

Every open PR was test-merged. 25 merge cleanly; 22 NKI PRs conflict only on their own results/logs/autotune_logs/<op>_autotune.json; none has a semantic conflict. 46 add files under pre-refactor paths and are migrated with:

git -c merge.directoryRenames=false merge origin/main
python scripts/migrate_pr_paths.py

Disabling directory-rename detection matters: with it on, git offers the wrong destination for impl_nki.py.

The 23 open NKI PRs carry over without a data fix. Each edits two results/csv/<op>_{default,autotune}.csv files to append torch_nki_ms, nki_ms and speedup_nki. #307 was test-merged onto this branch: git follows the rename and applies the edit to results/B200/csv/weight_dequant_{default,autotune}.csv; the three columns are kept; all 60 rows of each file keep their frozen PyTorch, Triton, cuTile and TileLang values; speedup_nki equals torch_nki_ms / nki_ms in 60 of 60 rows; and the full suite passes on the merged tree, B200 guard included. The only conflict is the known one on the PR's own results/logs/autotune_logs/<op>_autotune.json, a raw log that main no longer tracks and that belongs on the archive branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA

bowencui123 and others added 12 commits September 18, 2026 23:44
… benchmarks/problems

benchmarks/problems/current/<op>_current.md is a prompt input of the LLM
codegen pipeline, not a benchmark asset. Move the 45 descriptions to
tools/llm_codegen/problems/ and repoint prompt_builder and
generate_descriptions. The 16 unused files under problems/original are
removed. Rebuilt iter-0 prompts keep a description section identical to the
archived campaign prompts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit bbd8b61)
results/logs/ was force-added despite being git-ignored. Untrack the 272
raw timing/autotune JSON files; the summary CSVs stay tracked.
scripts/archive_logs.sh snapshots the local results/logs/ onto
archive/raw-logs-2026-09-18, building each commit as <origin/main tree> +
results/logs/ in a throwaway index so that branch always equals main plus
the raw logs. Nothing is checked out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit fec244e)
Drop the .gitkeep placeholder (the directory holds B200.json and
Trainium2.json). check_peak_specs.py now reads the measured peak bandwidth
and per-dtype peak TFLOPS from B200.json instead of hardcoding the
8000 GB/s datasheet value.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit 4b5f139)
PR #98 cherry-picked the llm_codegen code without its data, leaving main
with two pilot operators from #95. Restore the campaign from
exp/llm_and_analysis: 44 operators x {gpt-5.5, claude-opus-4-7}/high, each
with iter_N/ prompts, responses, kernels, feedback and token usage plus
final/ and run_summary.json. Token counts match the archived RQ4 trajectory
data iteration by iteration.

Removed: the pipeline-development runs kept from #95
(flash_attention high_CHEAT / high_no_skill) and the vector_add directory.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit 30ff150)
Bring back vector_add/{gpt-5.5,claude-opus-4-7}/high so the archive covers
all 45 operators of the paper's LLM track. benchmarks/llm_generated is now
identical to the tree on exp/llm_and_analysis.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit d33b2fc)
…ills/

prompt_builder read the API references from .claude/skills/, which is not
tracked, so a fresh clone built prompts without them. Add
skills/{triton,cutile}-guide/SKILL.md and read them from there.

The files are the versions the paper's campaign used (the cuTile guide
predates the later ct.static_iter section). Rebuilt iter-0 prompts now
reproduce the API-reference and description blocks of all 90 archived
prompts byte for byte.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit a00dfa0)
run_bench.py is the only writer of results/logs/ (run_bench_all.py writes to
results/runs/), so it now calls scripts/archive_logs.sh once the timing and
autotune JSON are written. The snapshot is a local commit on the archive
branch; nothing is checked out or pushed, a failure only prints a warning,
and --no-archive skips it.

archive_logs.sh now accumulates: the tree is origin/main + the logs already
archived (remote and local tips) + results/logs/ from disk, with the disk
winning. A machine holding only some of the logs therefore adds to the
archive instead of replacing it. Offline runs fall back to the last fetched
refs, and the ref update is compare-and-swap.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit 3ebae04)
Builds the cross-operator sweep-max latency figure from results/csv and the
NCU catalogue. Split out of the README work so the layout refactor, which
rewrites this script's path handling, does not depend on a docs commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
Move the framework sources out of the repository root into one importable
package, so imports are unambiguous and package resources resolve from the
package location instead of the current working directory.

  core/              -> tilebench/core/
  data/              -> tilebench/data/
  benchmarks/        -> tilebench/benchmarks/
  tools/llm_codegen/ -> tilebench/llm_codegen/
  tilebench_run/     -> tilebench/profiling/   (source, catalogue)
  tilebench_run/ncu/ -> outputs/ncu/           (generated reports)

New tilebench/paths.py resolves every package resource from __file__:
operator configs, peak-performance JSON, LLM prompt inputs and the NCU
catalogue. Callers that hardcoded relative paths (engine.py's
benchmarks/operators/<op>/config.yaml), recomputed the repo root by parent
count, or embedded this machine's absolute checkout path (ten profiling
scripts) now go through it. scripts/ stay the user entry points and put the
repository root on sys.path themselves, so they no longer need PYTHONPATH=.
and work from any directory.

Peak performance is split by kind: the framework-consumed device specs stay
tracked at tilebench/data/peak_performance/<GPU>.json with unchanged
contents, while the detailed measure_peak.py sweep becomes a generated
output under the git-ignored outputs/peak_performance/ and is no longer
tracked under results/.

tilebench/profiling/hf_upload.py gains a __main__ guard: it uploads at
import time, which was harmless for a loose script but not for a module of
an importable package.

Behaviour is unchanged. Verified: 45 operators discovered, all 45 config.yaml
parse to identical hashes, all 180 impl_* modules import, the 45 rebuilt LLM
prompts are byte-identical to the pre-move build, peak-performance and NCU
catalogue loads match, and 6429 of 6507 moved files are byte-identical (the
78 modified carry import/path edits only). Committed with --no-verify so the
whitespace hooks cannot rewrite 65 pre-existing files during a pure move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit 07c8540)
…ches

The open pull requests were written against the old source layout. This
moves any file still under a pre-refactor source directory onto the
tilebench package and rewrites the imports inside it, so each branch is a
merge plus one command:

    git merge -c merge.directoryRenames=false origin/main
    python scripts/migrate_pr_paths.py

Disabling directory-rename detection is load-bearing. With it on, git
guesses a destination for a file added inside a renamed directory and picks
the wrong one for the NKI operator PRs, offering
tilebench/benchmarks/llm_generated/<op>/<model>/high/final/impl_nki.py
instead of tilebench/benchmarks/operators/<op>/impl_nki.py.

Verified on #294 (single operator), #303 (cross-operator import) and #259
(45 operators at once). Temporary: delete it once those branches land.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit 0fa58d1)
results/ now tracks only the summary CSVs. The per-operator figures, the
aggregate tables and runtime_summary.md are regenerated by visualize.py and
aggregate_results.py, so they leave version control; outputs/ was already
declared the generated-artifact tree by the layout refactor but 49 NCU
writeups and probe results were still tracked inside it.

Nothing is deleted from the working tree: the 12 GB of .ncu-rep reports, the
NCU writeups, the aggregates and the figures all stay on disk, now ignored.

.gitignore previously ignored results/csv/ — exactly the one thing that
should be version controlled — while the generated siblings were tracked by
force-add. It now ignores results/* and re-includes results/csv/, verified
with git check-ignore on existing, new and nested CSV paths.

probe_kernel_count.py and ncu_writeup.py gained the mkdir their siblings
already had: with outputs/ untracked, a fresh clone has no outputs/ncu/ to
write into, and probe_kernel_count.py would otherwise fail after completing
the whole probe.

CSV contents are untouched: the 90 tracked files and their checksums are
identical before and after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit b645387)
kernel_counts.json records how many kernels one impl.run() launches per
(op, dtype, backend) and under which names. The NCU harness validates every
capture against it, so it is profiling input rather than a profiling report
and belongs beside ncu_catalogue.json, not in the git-ignored outputs/ tree
it shared with the 12 GB of .ncu-rep files.

  outputs/ncu/kernel_counts.json -> tilebench/profiling/kernel_counts.json

The file is copied byte for byte (sha256
cfe0687e48665968e09e62ac3def2e86ae3bed14aa4a12a46175dcce79b666a6, 118850
bytes, 224 entries covering all 45 operators); no probe was re-run and no
value was touched. tilebench.paths gains KERNEL_COUNTS, so every reader
resolves it from the package instead of the working directory.

Missing metadata is no longer silent. Both drivers used to skip the file
when absent and then assume one kernel per launch, which disables
wrong-kernel detection without saying so. ncu_kernel_select now owns the
loader: an absent file raises MissingKernelCountsError, which the two CLIs
turn into a clear message naming probe_kernel_count.py, while an individual
unprobed pair still falls back to 1 and warns on stderr, since a single
pair that errored during probing should not abort a whole sweep.

probe_kernel_count.py writes the canonical path. A full run rewrites it as
before; an ONLY_OP run now merges that operator's rows into the existing
file, which previously truncated it to the probed operator alone. The
probing algorithm, operator set, launch-count detection and JSON schema are
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit c153413)
bowencui123 and others added 13 commits September 19, 2026 04:02
…ooling

tilebench/benchmarks/llm_generated/ stays the LLM pipeline's default output
directory, but its contents are experiment artifacts rather than source, so
main now git-ignores it. LLM_GENERATED_ROOT and generate.py are unchanged:
same runtime path, different storage policy. The 6140 files are removed from
the index only; nothing is deleted from the working tree.

Backup: scripts/archive_artifacts.sh --logs | --llm | --all [--push]
generalises archive_logs.sh. It still builds the commit in a throwaway
index without checking anything out, and the archive is cumulative: every
archive-owned path (results/logs/, tilebench/benchmarks/llm_generated/ and
the pre-refactor benchmarks/llm_generated/ snapshot) is carried forward from
the archive tip, so a machine holding only part of the artifacts adds to the
backup instead of replacing it. archive_logs.sh remains as a wrapper for
--logs, so run_bench.py keeps archiving raw logs only and never commits LLM
trajectories as a side effect of a benchmark. Files are staged with
update-index rather than git add -f: exclude pathspecs on ignored
directories misbehave on git 2.27, and interpreter caches must stay out.

Distribution: artifacts/manifest.json declares downloadable artifacts (URL,
SHA256, archive name, provided directories). scripts/fetch_artifacts.py
downloads one, verifies the checksum, unpacks into a staging directory and
moves it into place, refusing to replace a non-empty destination without
--force and rejecting archives with traversal, links or members outside the
declared directories. Standard library only; Google Drive share links are
rewritten to direct downloads. scripts/package_artifacts.py builds the
reproducible tarball and prints the checksum for the manifest. The
llm-aacl2026 entry keeps explicit placeholders for URL and SHA256 until the
upload exists, and the downloader reports that instead of guessing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit 5c74683ebfaeba81268a1bcb665a3a8233ab9d82)
The paper's LLM artifact is uploaded, so the manifest placeholders become the
file's Google Drive share link and the SHA256 of the packaged archive. A real
download through fetch_artifacts.py verified the checksum and restored 6140
files identical to the source tree. The manifest test now accepts either a
fully published entry or an explicit placeholder, never a partial one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
(cherry picked from commit 042f6d8d0460cc31cc0724cccf0d75a84b797fda)
Path-only migration ahead of hardware-scoped results. All 90 committed CSVs
(45 operators x {default, autotune}) are B200 measurements, so they move from
results/csv/ to results/B200/csv/. Pure rename: no file is regenerated,
reformatted or newline-normalised (56 of them use CRLF). Committed with
--no-verify so the whitespace hooks cannot touch the data.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
TileBench will hold results for several GPUs (B200 today; GH200 and an AMD
part to follow), and a global results/{csv,logs,figures,aggregate,runs}
lets the same operator overwrite itself across machines. Everything is now
results/<gpu>/{csv,logs,figures,aggregate,runs}; only csv/ is tracked.

- tilebench/paths.py: results_root / results_{csv,logs,figures,aggregate,
  runs}_dir and hardware_label(), which accepts any single safe path
  component. There is no device list: a new GPU needs a label, not a patch.
- run_bench.py, run_bench_all.py, visualize.py, plot_sweep_max.py,
  aggregate_results.py, ncu_catalogue.py take --gpu. It is required and has
  no default, so a run on another machine cannot land in B200's namespace.
  run_bench.py prints the namespace next to the detected device and warns
  when the label does not appear in the device name.
- A GPU CSV holds measurements from that GPU only. The TileLang merge is
  unchanged but confined to results/<gpu>/csv/. NKI (AWS Trainium) can no
  longer be merged into, or run together with, GPU backends: it is selected
  alone, without --gpu, and writes to outputs/nki/. Its profiler artifacts
  move there from results/logs/ too. The default backend set, and 'all', is
  triton, cutile, tilelang.
- plot_sweep_max.py writes to results/<gpu>/figures/ by default so plotting
  another GPU cannot overwrite the README figure; pass --output for that.
- .gitignore tracks results/<hardware>/csv/**/*.csv for any label and keeps
  logs, figures, aggregates (CSV too) and runs ignored.
- archive_artifacts.sh --logs takes --gpu and snapshots results/<gpu>/logs/
  only. Logs of every other GPU on the archive branch, and the legacy
  results/logs/ and benchmarks/llm_generated/ snapshots, are carried forward.
  run_bench.py archives the logs of its own --gpu.
- Tests: path validation, per-GPU defaults for every script, no collision
  between GPUs, TileLang merge isolation, NKI isolation, .gitignore rules,
  archive behaviour, and the 90 B200 CSVs checked against SHA256 recorded
  before the move.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
The previous commit isolated NKI under outputs/nki/. That is reverted: NKI
keeps its original result organisation, moved into the hardware-scoped
layout. `run_bench.py --gpu B200 --tile-language nki` is allowed again;
--gpu names the result namespace of the campaign and does not claim that
NKI ran on that GPU. NKI still runs through its own path on AWS Trainium,
and engine and kernel execution are unchanged.

- The NKI merge into results/<gpu>/csv/<op>_{default,autotune}.csv is
  restored as it is on main: torch_nki_ms, nki_ms and speedup_nki are
  appended as measured, with no B200 drift scaling, and
  speedup_nki = torch_nki_ms / nki_ms, never torch_ms / nki_ms.
- NKI timing and autotune JSON go to results/<gpu>/logs/ like every other
  log. The Neuron profiling artifacts move from results/logs/ to
  results/<gpu>/logs/nki_profiles/ and nki_neff_manifest.jsonl: the engine
  takes logs_dir and hands both paths to the orchestrator, which no longer
  has a default location.
- --gpu is required again for every run. NKI still only runs when named:
  the default selection and 'all' stay triton, cutile, tilelang.
  run_bench_all.py goes back to the engine's own backend selection.
- archive_artifacts.sh --logs --gpu <gpu> snapshots the whole logs/ tree,
  NKI profiles included. There is no separate NKI archive path.
- Tests: NKI merges into the B200 CSV, its columns survive a later TileLang
  merge, frozen GPU columns and the GH200 CSV stay untouched, speedup_nki
  uses torch_nki_ms, NKI logs and profiles land under results/<gpu>/logs/
  and are archived with it. The B200 guard now accepts the NKI extension:
  the manifest records the pre-move SHA256 and a digest of the frozen
  columns, so a file is either byte-identical or extended with its GPU
  columns intact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
Path-only move ahead of hardware-scoped profiling metadata. The catalogue
(sweep-max cases and autotune winners) and the probed kernel counts were
recorded on B200, so they move from tilebench/profiling/ to
tilebench/profiling/metadata/B200/. No content change. Committed with
--no-verify: neither file ends in a newline, and the end-of-file hook
would otherwise rewrite them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
Four places still assumed a single GPU or a single run per operator.

Raw JSON. results/<gpu>/logs/ held one <op>_results.json and one
<op>_autotune.json, so a default run, an autotune run, a TileLang-only run
and an NKI run overwrote each other. The default names now carry the mode
and the backend selection: <op>_<mode>_<backends>.json, built only by
tilebench.paths.timing_log_path / autotune_log_path. tilebench/backends.py
owns the backend names, their canonical order and the --tile-language
parser, so `cutile,triton` and `triton,cutile` are one tag. Explicit
--output / --autotune-log still win. With one file per selection the
autotune-log merge is gone: it existed to stop a TileLang run clobbering
the Triton/cuTile winners, and would now refuse a rerun with other cases.
visualize.py names the run it reads with --mode and --tile-language, and
ncu_catalogue.py reads exactly <op>_autotune_<backends>.json (default
triton,cutile; both are required). Nothing is chosen by glob or mtime.

NCU metadata. The catalogue and the kernel counts were one global pair,
although sweep-max cases, autotune winners, launch counts and kernel names
differ between GPUs. They are now
tilebench/profiling/metadata/<gpu>/{ncu_catalogue,kernel_counts}.json
(the B200 files moved in the previous commit), reached through
ncu_catalogue_path / kernel_counts_path. NCU_CATALOGUE and KERNEL_COUNTS
are removed. ncu_catalogue, probe_kernel_count, ncu_one, ncu_driver,
ncu_writeup, hf_upload and plot_sweep_max take --gpu, required and without
a default. A GPU without metadata is an error that names the command to
produce it; another GPU's files are never used. Reports go to
outputs/ncu/<gpu>/, and hf_upload uploads under ncu_report_main/<gpu>/ so
it cannot overwrite the released B200 reports. package-data covers the
nested directory, checked by a non-editable install.

Archive. main still tracks the legacy results/logs/, so reading main's tree
would bring it back on every run. It is now dropped from the base tree, and
results/logs/ on an archive tip is re-rooted at results/B200/logs/ byte for
byte. A migrated file that meets a different file at its destination,
already archived or in the working directory, aborts the run with nothing
committed. The logs of other GPUs and both LLM paths are carried forward as
before. The summary CSVs come from main's tree and are never copied.

Portability. The profiling shell scripts located the repository through a
hard-coded /projects/... path, a user-specific conda path and QOS, and
PYTHONPATH=. They resolve it from their own location (batch2.sbatch from
the submit directory) and work from any directory. run_batch.sh no longer
renames or copies results: run_bench.py owns the names, and the
<op>_summary.csv it copied has not existed for a while. run_bench.py starts
the archive script inside the repository whatever the caller's directory.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
tilebench/ is the installable Python source. tilebench/profiling/ still held
cluster launch scripts and measured data, which are neither.

- Remove batch1_launch.sh, batch2.sbatch, rerun_timeout.sh, run_batch.sh and
  supplemental_launch.sh. They are operational scripts of one campaign on
  one cluster; no source file, script or test of the framework depends on
  them. They are kept on the archive branch, not moved to scripts/.
- Move the NCU metadata out of the package. ncu_catalogue.json (autotune
  winners) and kernel_counts.json (probed launch counts and kernel names) are
  measured on one GPU and written by ncu_catalogue.py / probe_kernel_count.py:
  generated experiment data, not a package resource. No benchmark code path
  reads them; only the NCU tools do. They now live under the Git-ignored
  outputs/profiling/<gpu>/, next to the NCU reports, still one directory per
  GPU and still with no fallback to another GPU. The committed B200 files
  leave the tree and are kept on the archive branch.
- plot_sweep_max.py no longer needs that metadata: the sweep-max case comes
  from the operator's config.yaml through the same rule the catalogue uses
  (ncu_catalogue.sweep_max_cases), so the README figure is reproducible from
  the configs and the committed CSVs alone.
- pyproject.toml: drop the tilebench.profiling package-data entry. Wheel and
  sdist now carry only .py files for that package.
- Tests: drop the two tests that only exercised the removed shell scripts,
  and the SHA256 fingerprints of the B200 CSVs and of the B200 metadata
  (tests/data/ is gone). They proved one migration, not a software contract.
  The CSV schema test, the namespace and isolation tests and the NKI merge
  tests stay. A packaging test now asserts that a regular install ships no
  shell script and no data under tilebench/profiling/.
- archive_artifacts.sh: carry forward outputs/profiling/ and any launch
  script kept on the archive tip, and keep the tip's own tree as the base
  when it already contains origin/main, so code merged into the archive from
  a branch ahead of main is not rolled back by the next archive run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
tilebench/profiling/ still mixed three kinds of Python file. "All .py" is
not the same as "package source", so each of the 17 files was classified by
what it does and who imports it.

Library, stays in the package (3):
  __init__.py
  ncu_kernel_select.py   kernel selection, capture validation and metadata
                         loading, imported by four tools
  ncu_catalogue.py       sweep-max cases and catalogue entries; imported by
                         plot_sweep_max.py. Its argparse main and sys.path
                         hack are gone; write_catalogue() is the entry point.

Command-line tools, moved to scripts/ (8):
  scripts/aggregate_results.py            aggregates result CSVs, so it sits
                                          with visualize.py, not with NCU
  scripts/profiling/ncu_catalogue.py      thin CLI over the library
  scripts/profiling/{ncu_driver,ncu_one,ncu_writeup,probe_kernel_count,
                     hf_upload}.py        had no importer outside the tests
  scripts/profiling/ncu_generic_harness.py  never imported: it is the process
                                          NCU profiles, started by path
They bootstrap sys.path like the other scripts and run from any directory
without PYTHONPATH. The drivers locate the harness next to themselves
instead of inside the package. The report path rule both drivers duplicated
is now tilebench.paths.ncu_report_path.

Campaign-specific, removed from the public tree (7), kept on the archive:
  probe_hung_cfgs.py and six ncu_harness_*.py. Hard-coded operators, cases
  and configs, code that runs at import, the pre-profiler-range methodology
  the generic harness replaced, and no reference from any driver, test or
  document.

NCU behaviour, metadata and output paths are unchanged: a real capture of
mul2 on a B200, started from /tmp with the relocated tools, validated 1/1
launches. archive_artifacts.sh now carries forward whatever the archive tip
keeps under tilebench/profiling/ that the base tree dropped, not only
*.sh/*.sbatch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
A benchmark run only produces files. By default run_bench.py also fetched
origin/main and the 7,000-file archive branch, and committed a snapshot of
the raw logs to a local branch of the user's clone; the README had to tell
readers to pass --no-archive on every command. Backing results up is a
maintainer task that targets one dated archive branch, not a runtime feature.

- run_bench.py: remove _archive_logs(), the --no-archive flag and the call
  after the run, with the subprocess and REPO_ROOT imports only they used.
  There is no opt-in replacement and no compatibility no-op.
- Remove scripts/archive_artifacts.sh, scripts/archive_logs.sh and
  tests/test_archive_scripts.py. They are kept on the archive branch, where
  archive_artifacts.sh remains the maintenance tool of that branch.
- results/<hardware>/logs/ stays Git-ignored: a run writes its logs there and
  performs no Git operation.
- Tests: drop the archive hook from the run_bench fixture and the test of the
  hook itself; remove --no-archive from every call. A new test asserts that a
  run starts no process and that --no-archive and --archive are rejected.
  Namespace isolation, NKI merge, path layout and raw JSON collision tests
  are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
Keep the assertion that matters, that run_bench.py starts no process, and
drop the check that two archive flags are rejected: argparse rejects any
unknown flag, and the public tree should not name an option it never had.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
… to llm

Path-only. tilebench/llm_codegen/problems/ becomes tilebench/problems/, a
sibling of benchmarks/, core/ and data/, and tilebench/llm_codegen/ becomes
tilebench/llm/. No file content changes; the next commit updates the
references. Committed with --no-verify: the task descriptions are prompt
inputs and must stay byte-identical, so the whitespace hooks must not touch
them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
Follows the path-only move. tilebench.paths gains PROBLEMS_ROOT and renames
LLM_CODEGEN_ROOT to LLM_ROOT; prompt_builder.py and generate_descriptions.py
read the task descriptions through PROBLEMS_ROOT instead of a directory next
to themselves. Module references become tilebench.llm, run as
`python -m tilebench.llm.generate`. tilebench/problems/ holds only Markdown,
so pyproject.toml declares it as data of the top-level package, and
migrate_pr_paths.py maps main's benchmarks/problems/current/ and
tools/llm_codegen/ to the new locations.

The 45 rebuilt initial prompts are byte-identical to the ones built before
the move. A wheel and an sdist carry the 45 task descriptions and the llm
package; tests assert that every operator has a description under
PROBLEMS_ROOT and that a regular install ships them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdNBRsQvET5PUJ3Nw1hVCA
@Jokeren
Jokeren merged commit 06320a6 into main Sep 20, 2026
@Jokeren
Jokeren deleted the bowen/infra/repo-cleanup branch September 20, 2026 01:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants