The front half of FlyDSL kernel optimization on AMD CDNA (gfx950 / MI350X).
Give it a kernel name; it runs rocprofv3, decodes the trace, and returns a complete
instruction-level diagnosis — every stall bubble, where it lives in the .py, and
which FlyDSL thread-level knob addresses it — as a stable JSON bundle + a REPORT.md.
It does not optimize the kernel. It produces the evidence to — and it is built to be
driven by an agent: one deterministic CLI, one JSON envelope per command, a clear
input→output at every step, paired with companion skills under skills/.
pip install -e . # console script: flyprof (dep: pyyaml)
flyprof doctor # rocprofv3 / GPU / build-tree / soc → verdict: ready
flyprof list # which kernels can I profile?
flyprof run softmax --shape 32768,8192,bf16 --gpu 0 # the whole pipeline → REPORT.mdPoint at your FlyDSL checkout with --worktree (default $FLYPROF_WORKTREE or
/sgl-workspace/FlyDSL-lab). flyprof run is the convenience path; step through the
primitives when you want to inspect or re-run a single stage.
The whole tool is one shape: input a kernel name, output one envelope, with state in a bundle dir. That's what makes it agent-drivable — every command is read the same way.
Input — flyprof <command> [<kernel>] [--shape M,N[,K],dtype] [--gpu N] [--worktree PATH] [--bundle DIR] [--format json|table]
Output — exactly one envelope on stdout, every command, every time:
{ "ok": true, "command": "capture", "bundle": "/abs/dir",
"data": { …command-specific… }, "warnings": [], "truncated": {…}? }
{ "ok": false, "command": "capture",
"error": { "code": "EMPTY_ATT", "exitCode": 66, "message": "…", "help": "…", "available": […] } }- State flows through the bundle dir (see the diagram above): each command writes one JSON artifact; later commands read what earlier ones wrote.
- Exit codes are sysexits-style and bound to
error.code— branch on the number, not the prose:0ok ·66empty/no-match (a result, not a bug) ·69rocprofv3/GPU missing (STOP) ·75timeout (retry) ·78bad config / missing build tree ·70internal.
| command | input → output | GPU? |
|---|---|---|
list |
worktree → profilable kernels (curated recipes ∪ synthesizable) | no |
doctor |
→ rocprofv3 / GPU / build-tree / soc verdict | no |
tile |
shape + dtype + soc → recommended BM/BN/BK + LDS/VGPR/occupancy/roofline | no |
capture |
kernel → ATT bundle(s) + capture.json (dispatch, vgpr, occ, mapped%) |
yes |
counters |
kernel → roofline / occupancy / L2 / LDS / tri-state bound | yes |
bubbles |
bundle → stall taxonomy by class + hotspots + waitcnt attribution | no |
map |
bundle → ISA ↔ .py:line + source snippets + flamegraph |
no |
report |
bundle → ranked bubble→knob recommendations + REPORT.md / report.json |
no |
bundle |
bundle → canonical examples/<kernel>/ artifact |
no |
run |
the full pipeline on one kernel | yes |
flyprof isn't a new profiler — it wraps four ROCm/FlyDSL tools (and borrows opencli's
agent-CLI pattern) into a single workflow, so an agent never has to orchestrate them by hand.
A generic core (works on any rocprofv3 ATT trace) plus a thin FlyDSL-specific layer
(the knob vocabulary, recipe resolution, and DWARF source mapping). Everything is GPU-free
except capture and counters, which wrap rocprofv3.
report runs a rules engine (flyprof/knobs.py; full catalog in
skills/references/bubble-knob-table.md). Each
stall/bubble class → the diagnostic signal that fires it → the FlyDSL knob → the concrete
change, gated by the roofline bound and carrying evidence (the field that fired +
the .py:line the bubble maps to), so every claim is auditable back to the ISA.
On honesty. The bubble taxonomy and counter ratios (L2 hit, 32B-partial fraction,
MFMA fraction) are reliable and drive the decisions. Absolute HBM bandwidth from raw PMC
needs per-XCD/channel normalization we don't fully reproduce, so it's reported as a lower
bound flagged calibrated: false and never used to decide the bound.
examples/ holds real output bundles. The flagship is
examples/flash_attn_fwd/ — FlyDSL's dual-wave
software-pipelined flash attention, the hardest operator: arch_vgpr 249 → 4 waves/CU,
diagnosed occupancy-capped with the root-cause fix ranked first (cut register
footprint), traceable to flash_attn_gfx950.py. A full ATT trace ships with it — load it
with flyprof bubbles/map --bundle examples/flash_attn_fwd. (Profiling it found a stale
recipe in the hub, fixed in flydsl-kernel-profiling#8.)
skills/ holds the runbooks an agent follows — flyprof-usage (router) →
flyprof-capture, flyprof-analyze, flyprof-report, flyprof-triage — plus references
(bubble-knob-table.md, rocprofv3-quirks.md). Copy/symlink into .claude/skills/ to register.
- Standalone. The harness logic (
att_capture, hotspot reg-pressure, …) is vendored + de-hardcoded here; no runtime dependency onflydsl-kernel-profiling. To publish a bundle into that hub, pass--examples-dir <hub>/examples(the default is repo-localruns/examples/). - Diagrams are generated — edit
docs/diagrams.pyand runpython docs/diagrams.pyto regenerate the SVGs (they stay aligned and track the command surface). - Validated on MI350X/gfx950 (rocprofv3 1.1.0):
flyprof run softmax→ memory-bound, vmcnt-dominated (49% of 77% stall) → "software-prefetch / double-buffer the global loads," traceable tosoftmax_kernel.py.pytest tests/(19 tests) exercises the tile math, the headless bubble taxonomy + ISA↔source map (committed fixture), the knob rules, and the envelope contract — no GPU needed.
flyprof run softmax → bound: memory · rank-1: vmcnt → prefetch · @ softmax_kernel.py:43