flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
kernel_diff.py
Go to the documentation of this file.
1#!/usr/bin/env python3
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
4the transfer gate).
5
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.
9
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)
19
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.
23"""
24import csv
25import os
26import re
27import sqlite3
28import sys
29
30A = sys.argv
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
34
35
36def short_nsys(name):
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 "")
49
50
51def load_nsys(prefix):
52 out = {}
53 for r in csv.DictReader(open(prefix + "_cuda_gpu_kern_sum.csv")):
54 k = short_nsys(r["Name"])
55 n, t = out.get(k, (0, 0.0))
56 out[k] = (n + int(r["Instances"]), t + float(r["Total Time (ns)"]) * 1e-9)
57 return out
58
59
60def load_kp(path):
61 """kernel-timer text: '- <label>' then ' (<type>) <total s> <calls> <avg> ...'."""
62 out, label = {}, None
63 for line in open(path):
64 s = line.strip()
65 if s.startswith("- "):
66 label = s[2:].strip()
67 elif label and s.startswith("("):
68 f = s.split(")", 1)[1].split()
69 out[label] = (int(f[1]), float(f[0]))
70 label = None
71 return out
72
73
74def memcpy_hist(prefix):
75 db = prefix + ".sqlite"
76 if not os.path.exists(db):
77 return {}
78 con = sqlite3.connect(db)
79 names = dict(con.execute("SELECT id, label FROM ENUM_CUDA_MEMCPY_OPER"))
80 h = {}
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
86 return h
87
88
89a, b = (load_nsys(PA), load_nsys(PB)) if MODE == "nsys" else (load_kp(PA), load_kp(PB))
90rows = []
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}")
102if MODE == "nsys":
103 ha, hb = memcpy_hist(PA), memcpy_hist(PB)
104 if ha or hb:
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
108 if d:
109 print(f" {key[0]:28s} {key[1]:>8s} {d:8.2f}")
short_nsys(name)
memcpy_hist(prefix)
load_kp(path)
load_nsys(prefix)