| 381 | # heuristic flags — turn raw numbers into "where is the margin" |
| 382 | # -------------------------------------------------------------------------------------- |
| 383 | def optimization_flags(summary: dict) -> list[str]: |
| 384 | flags = [] |
| 385 | phases = summary.get("phases_mean", {}) |
| 386 | step = summary.get("phase_sum_ms_mean", 0) or 1 |
| 387 | for name, ms in sorted(phases.items(), key=lambda kv: kv[1], reverse=True)[:2]: |
| 388 | if ms / step > 0.30: |
| 389 | flags.append(f"`{name}` is {ms/step*100:.0f}% of the step ({ms:.1f} ms) — primary target.") |
| 390 | n = summary.get("nsys") |
| 391 | if n and not n.get("error"): # don't reason from a failed/zeroed nsys pass |
| 392 | if n.get("launches_per_token", 0) > 50: |
| 393 | flags.append(f"{n['launches_per_token']} kernel launches/token — kernel-fusion candidate " |
| 394 | f"(launch overhead dominates many tiny kernels).") |
| 395 | if n.get("memcpy_ms_per_token", 0) > 0.5: |
| 396 | flags.append(f"{n['memcpy_ms_per_token']:.2f} ms/token in host<->device copies — " |
| 397 | f"CPU/GPU-overlap candidate (data shuttling off the critical path).") |
| 398 | return flags |
| 399 | |
| 400 | |
| 401 | # -------------------------------------------------------------------------------------- |