2"""Per-step kernel launches and times from two profiles of the same run at different lengths
3(the VoF step-performance harness, WO-0 -- doc/vof_step_performance_design.md sec. 8 G-PERF and
6A profile covers the whole process (build, warm-up, timed steps). Two runs that differ ONLY in the
7number of timed steps (e.g. prof.py 10 --warm 3 and prof.py 30 --warm 3) differ by exactly those
8steps, so (B - A) / steps is the per-step cost with setup and warm-up cancelled.
10 # GPU: nsys, then its csv summaries
11 nsys profile --trace=cuda -o n10 python prof.py 10 --warm 3 --pcg --flux device (and n30)
12 nsys stats --report cuda_gpu_kern_sum,cuda_gpu_mem_size_sum --format csv --output n10 n10.nsys-rep
13 python kernel_diff.py nsys n10 n30 --steps 20 [--top 25]
14 # host: the Kokkos simple kernel timer (kokkos-tools profiling/simple-kernel-timer)
15 KOKKOS_TOOLS_LIBS=.../kp_kernel_timer.so python prof.py 10 ... > k10.log 2>&1 (and k30)
16 python kernel_diff.py kp k10.log k30.log --steps 20 [--top 25]
17 (the 2024-09 timer prints its table to stdout at finalize and writes no .dat; an older one
18 writes <host>-<pid>.dat, which kp_reader turns into the same text)
20nsys mode names a kernel by the function that launched it (Kokkos labels are not visible under
21--trace=cuda); kp mode by its Kokkos label. nsys mode also prints the D->H / H->D memcpy count per
22step and a size histogram, from the cuda_gpu_mem_size_sum reports and the .sqlite exports.
31MODE, PA, PB = A[1], A[2], A[3]
32STEPS = int(A[A.index(
"--steps") + 1])
if "--steps" in A
else 20
33TOP = int(A[A.index(
"--top") + 1])
if "--top" in A
else 25
37 """'void Kokkos::Impl::cuda_parallel_launch_...<Kokkos::Impl::ParallelFor<peclet::flow::X::f(...)
38 ::[lambda ... (instance 2)] ...' -> 'X::f#2' (the launching function, lambda instance)."""
39 m = re.search(
r"Parallel(?:For|Reduce|ScanWithTotal|Scan)<(.*)", name)
40 body = m.group(1)
if m
else name
41 body = re.sub(
r"^(Kokkos::Impl::Combined\w+<)+",
"", body)
42 body = re.sub(
r"^void ",
"", body)
43 m = re.match(
r"([\w:]+(?:<[^()]*?>)?(?:::[\w]+)*)", body)
44 fn = m.group(1)
if m
else body[:60]
45 fn = re.sub(
r"<.*>",
"", fn).replace(
"peclet::flow::",
"")
46 inst = re.search(
r"\(instance (\d+)\)", body)
47 kind =
"R:" if "ParallelReduce" in name
else (
"S:" if "ParallelScan" in name
else "")
48 return kind + fn + (f
"#{inst.group(1)}" if inst
and inst.group(1) !=
"1" else "")
53 for r
in csv.DictReader(open(prefix +
"_cuda_gpu_kern_sum.csv")):
55 n, t = out.get(k, (0, 0.0))
56 out[k] = (n + int(r[
"Instances"]), t + float(r[
"Total Time (ns)"]) * 1e-9)
61 """kernel-timer text: '- <label>' then ' (<type>) <total s> <calls> <avg> ...'."""
63 for line
in open(path):
65 if s.startswith(
"- "):
67 elif label
and s.startswith(
"("):
68 f = s.split(
")", 1)[1].split()
69 out[label] = (int(f[1]), float(f[0]))
75 db = prefix +
".sqlite"
76 if not os.path.exists(db):
78 con = sqlite3.connect(db)
79 names = dict(con.execute(
"SELECT id, label FROM ENUM_CUDA_MEMCPY_OPER"))
81 for kind, size
in con.execute(
"SELECT copyKind, bytes FROM CUPTI_ACTIVITY_KIND_MEMCPY"):
82 b =
"<=8B" if size <= 8
else "<=1KiB" if size <= 1024
else "<=64KiB" if size <= 65536 \
83 else "<=1MiB" if size <= 1 << 20
else ">1MiB"
84 key = (names.get(kind, str(kind)), b)
85 h[key] = h.get(key, 0) + 1
91for k
in set(a) | set(b):
92 na, ta = a.get(k, (0, 0.0))
93 nb, tb = b.get(k, (0, 0.0))
94 rows.append((k, (nb - na) / STEPS, 1000.0 * (tb - ta) / STEPS))
95rows.sort(key=
lambda r: -r[2])
96tot_n = sum(r[1]
for r
in rows)
97tot_t = sum(r[2]
for r
in rows)
98print(f
"per step over {STEPS} steps ({MODE}): {tot_n:.1f} launches, {tot_t:.3f} ms kernel time")
99print(f
"{'launches/step':>14} {'ms/step':>9} kernel")
100for k, n, t
in rows[:TOP]:
101 print(f
"{n:14.2f} {t:9.3f} {k}")
105 print(
"memcpy per step (kind, size bin): count")
106 for key
in sorted(set(ha) | set(hb)):
107 d = (hb.get(key, 0) - ha.get(key, 0)) / STEPS
109 print(f
" {key[0]:28s} {key[1]:>8s} {d:8.2f}")