trace_gaps.py 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940
  1. #!/usr/bin/env python3
  2. """GPU idle-gap analysis of a rank-0 torch-profiler trace.
  3. Merges kernel intervals from all streams, lists the largest idle gaps with the
  4. kernels on either side, and sums idle time by (before -> after) kernel pair.
  5. usage: trace_gaps.py TRACE_DIR STEPS [MIN_GAP_US]
  6. """
  7. import collections, glob, gzip, json, sys
  8. d, steps = sys.argv[1], int(sys.argv[2])
  9. min_gap = float(sys.argv[3]) if len(sys.argv) > 3 else 20.0
  10. f = sorted(glob.glob(d + "/*TP-0*.gz"))[-1]
  11. ev = [e for e in json.load(gzip.open(f))["traceEvents"]
  12. if e.get("ph") == "X" and e.get("cat") in ("kernel", "gpu_memcpy", "gpu_memset")]
  13. ev.sort(key=lambda e: e["ts"])
  14. span = ev[-1]["ts"] + ev[-1]["dur"] - ev[0]["ts"]
  15. busy_union = 0.0
  16. gaps = []
  17. cur_s, cur_e, last = ev[0]["ts"], ev[0]["ts"] + ev[0]["dur"], ev[0]
  18. for e in ev[1:]:
  19. s, t = e["ts"], e["ts"] + e["dur"]
  20. if s > cur_e:
  21. busy_union += cur_e - cur_s
  22. if s - cur_e >= min_gap:
  23. gaps.append((s - cur_e, last["name"][:70], e["name"][:70]))
  24. cur_s, cur_e = s, t
  25. else:
  26. cur_e = max(cur_e, t)
  27. if t >= cur_e:
  28. last = e
  29. busy_union += cur_e - cur_s
  30. idle = span - busy_union
  31. print("span %.1f ms (%.2f/step) GPU busy(union) %.1f ms idle %.1f ms (%.1f%%, %.2f ms/step) gaps>=%.0fus: %d"
  32. % (span / 1e3, span / 1e3 / steps, busy_union / 1e3, idle / 1e3, 100 * idle / span, idle / 1e3 / steps, min_gap, len(gaps)))
  33. pair = collections.defaultdict(lambda: [0.0, 0])
  34. for g, a, b in gaps:
  35. pair[(a, b)][0] += g; pair[(a, b)][1] += 1
  36. print("idle by boundary (ms/step, count):")
  37. for (a, b), (g, c) in sorted(pair.items(), key=lambda x: -x[1][0])[:15]:
  38. print(" %6.2f ms/step %4d %s -> %s" % (g / 1e3 / steps, c, a, b))