How-to: profile a kernel¶
Diagnostics are off by default and are enabled per run. Turn on the one that answers your question — each writes its artifacts under the run's output directory, and each has an analysis CLI that reads them.
Start by asking which question you have:
| Question | Enable | Then read |
|---|---|---|
| Where did wall-clock go, host vs device? | nothing — [STRACE] markers are always emitted |
L2 Timing, host trace |
| Which task ran when, on which core? | --enable-l2-swimlane |
L2 Swimlane Profiling |
| Is the scheduler the bottleneck, or starved? | --enable-l2-swimlane --enable-dep-gen --enable-swimlane-overhead |
Scheduler-Overhead Model |
| Why is one task slow inside the core? | --dump-args, then the L0 tool |
L0 Swimlane Profiling |
| What do the AICore hardware counters say? | --enable-pmu |
PMU Profiling |
| What does the dependency graph actually look like? | --enable-dep-gen |
dep_gen |
| Am I near a ring / dep-pool capacity limit? | --enable-scope-stats |
Scope Stats |
| What arguments did a task really receive? | --dump-args |
Args Dump |
Enabling from pytest¶
# per-task timing across cores (bare flag = level 4, full detail)
pytest examples/my_example --platform a2a3 --device 4 --enable-l2-swimlane
# scheduler overhead: needs the swimlane AND a dependency graph
pytest examples/my_example --platform a2a3 --device 4 \
--enable-l2-swimlane --enable-dep-gen --enable-swimlane-overhead
# hardware counters, and captured per-task arguments
pytest examples/my_example --platform a2a3 --device 4 --enable-pmu
pytest examples/my_example --platform a2a3 --device 4 --dump-args
--enable-swimlane-overhead requires --enable-l2-swimlane plus a deps.json;
if it is absent, re-run adding --enable-dep-gen.
--enable-l2-swimlane is L2-only. Asking for it on an L3 test is rejected
up front — scope to a single chip with --level 2, or drop the flag.
Use --rounds N when you want several iterations, so first-run effects (binary
load, cold arena) do not dominate what you measure.
Enabling from your own code¶
The same switches are CallConfig fields, so a direct-Worker program sets
them itself. Any enabled diagnostic requires output_prefix to be set —
CallConfig::validate() rejects the config otherwise.
cfg = CallConfig()
cfg.enable_l2_swimlane = 4 # 0 = off; 1..4 select detail
cfg.enable_pmu = 1 # 0 = off; >0 selects the event type
cfg.output_prefix = "outputs/my_run" # required once any diagnostic is on
worker.run(handle, args, cfg)
In a @scene_test, per-case knobs live under "config", including
aicpu_thread_num and — on tensormap_and_ringbuffer — runtime_env ring
sizing (ring_task_window, ring_heap, ring_dep_pool).
Reading the results¶
The analysis CLIs ship in the wheel under simpler_setup.tools; run them with
python -m. Full flag documentation is in
simpler_setup/tools/README.md.
| Tool | Consumes | Gives you |
|---|---|---|
strace_timing |
the run log | host/device wall-clock breakdown, per-round table |
swimlane_converter |
l2_swimlane_records_*.json |
a Perfetto trace plus a per-function task summary; --overhead overlays the scheduler-overhead counters |
sched_overhead_analysis |
swimlane + deps | whether the scheduler is the bottleneck or starved |
l0_swimlane |
an args dump | intra-core pipeline trace for one task |
deps_viewer, critical_path |
deps.json |
the dependency graph, and its critical path |
dump_viewer |
an args dump | the captured per-task arguments |
scope_stats_plot |
scope_stats.jsonl |
per-scope resource peaks |
python -m simpler_setup.tools.strace_timing <run.log> --rounds-table
python -m simpler_setup.tools.swimlane_converter <l2_swimlane_records_*.json>
Before optimizing anything¶
Check docs/investigations/ first. It records
proposals that were measured and dropped — several plausible-sounding
optimizations on the profiling and dispatch paths are already shut down there
with numbers, and re-deriving them costs a hardware round-trip.