phanerozoic commited on
Commit
7137be6
·
verified ·
1 Parent(s): 4611d12

Uploaded using `kernel-builder`.

Browse files
Files changed (46) hide show
  1. .gitattributes +9 -0
  2. build/torch211-cxx11-cu126-x86_64-linux/__init__.py +863 -0
  3. build/torch211-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  4. build/torch211-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  5. build/torch211-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  6. build/torch211-cxx11-cu126-x86_64-linux/metakernel/__init__.py +26 -0
  7. build/torch211-cxx11-cu128-x86_64-linux/__init__.py +863 -0
  8. build/torch211-cxx11-cu128-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  9. build/torch211-cxx11-cu128-x86_64-linux/_ops.py +9 -0
  10. build/torch211-cxx11-cu128-x86_64-linux/metadata.json +38 -0
  11. build/torch211-cxx11-cu128-x86_64-linux/metakernel/__init__.py +26 -0
  12. build/torch211-cxx11-cu130-x86_64-linux/__init__.py +863 -0
  13. build/torch211-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  14. build/torch211-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  15. build/torch211-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  16. build/torch211-cxx11-cu130-x86_64-linux/metakernel/__init__.py +26 -0
  17. build/torch212-cxx11-cu126-x86_64-linux/__init__.py +863 -0
  18. build/torch212-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  19. build/torch212-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  20. build/torch212-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  21. build/torch212-cxx11-cu126-x86_64-linux/metakernel/__init__.py +26 -0
  22. build/torch212-cxx11-cu130-x86_64-linux/__init__.py +863 -0
  23. build/torch212-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  24. build/torch212-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  25. build/torch212-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  26. build/torch212-cxx11-cu130-x86_64-linux/metakernel/__init__.py +26 -0
  27. build/torch212-cxx11-cu132-x86_64-linux/__init__.py +863 -0
  28. build/torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  29. build/torch212-cxx11-cu132-x86_64-linux/_ops.py +9 -0
  30. build/torch212-cxx11-cu132-x86_64-linux/metadata.json +38 -0
  31. build/torch212-cxx11-cu132-x86_64-linux/metakernel/__init__.py +26 -0
  32. build/torch213-cxx11-cu126-x86_64-linux/__init__.py +863 -0
  33. build/torch213-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  34. build/torch213-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  35. build/torch213-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  36. build/torch213-cxx11-cu126-x86_64-linux/metakernel/__init__.py +26 -0
  37. build/torch213-cxx11-cu130-x86_64-linux/__init__.py +863 -0
  38. build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  39. build/torch213-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  40. build/torch213-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  41. build/torch213-cxx11-cu130-x86_64-linux/metakernel/__init__.py +26 -0
  42. build/torch213-cxx11-cu132-x86_64-linux/__init__.py +863 -0
  43. build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_4611d12.abi3.so +3 -0
  44. build/torch213-cxx11-cu132-x86_64-linux/_ops.py +9 -0
  45. build/torch213-cxx11-cu132-x86_64-linux/metadata.json +38 -0
  46. build/torch213-cxx11-cu132-x86_64-linux/metakernel/__init__.py +26 -0
.gitattributes CHANGED
@@ -33,3 +33,12 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ build/torch211-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
37
+ build/torch211-cxx11-cu128-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
38
+ build/torch211-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
39
+ build/torch212-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
40
+ build/torch212-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
41
+ build/torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
42
+ build/torch213-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
43
+ build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
44
+ build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_4611d12.abi3.so filter=lfs diff=lfs merge=lfs -text
build/torch211-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch211-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:474c2f8fa87fd25a548c07014c46827295f5b2eace19b7e190f0090fa61e8877
3
+ size 626176
build/torch211-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch211-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
20
+ "_metakernel_cuda_4611d12.abi3.so": "R0wvj6h/0lpUjAcBTEaCcpX1surOGbfhkPAJD6YeiHc=",
21
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
22
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch211-cxx11-cu126-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu128-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch211-cxx11-cu128-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:741d3ad6ca34a13caade26ca26546a48c2a91c819c197dbb10e98336b5470d98
3
+ size 941440
build/torch211-cxx11-cu128-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch211-cxx11-cu128-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
22
+ "_metakernel_cuda_4611d12.abi3.so": "dB061so0oTyq3ibKJlRqSMKpHIGcGX27EOmDNrVHDZg=",
23
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
24
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch211-cxx11-cu128-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch211-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b2df8ca3e350475a7f0aebd96c8820a08c92a203bc890aa26182e7c4b8234386
3
+ size 961480
build/torch211-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch211-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
22
+ "_metakernel_cuda_4611d12.abi3.so": "st+Mo+NQR1p/CuvZbIggoIySogO8iQqiYYLnxLgjQ4Y=",
23
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
24
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch211-cxx11-cu130-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch212-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:95a7ad4c20e35be975f172307ed4ccfdfaacda544ffded4a0394a6c66451920c
3
+ size 636184
build/torch212-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch212-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
20
+ "_metakernel_cuda_4611d12.abi3.so": "laetTCDjW+l18XIwftTM/fqs2lRP/e1KA5SmxmRRkgw=",
21
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
22
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch212-cxx11-cu126-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch212-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c1808009535a071b8ee0439dafe12d3b6a84dc9be242f5c700957cffb9b26670
3
+ size 967576
build/torch212-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch212-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
22
+ "_metakernel_cuda_4611d12.abi3.so": "wYCACVNaBxuO4EOdr+EtO2qE3JviQvXHAJV8/7myZnA=",
23
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
24
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch212-cxx11-cu130-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e88576465f9b2bebeea6c417bf42418137f71e9029cbf52dc03e83f86e433a6d
3
+ size 980192
build/torch212-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch212-cxx11-cu132-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
22
+ "_metakernel_cuda_4611d12.abi3.so": "6IV2Rl+bK+vupsQXv0JBgTf3HpApy/UtwD6D+G5DOm0=",
23
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
24
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch212-cxx11-cu132-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch213-cxx11-cu126-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:97f07e5ff1283cbc7f715f1b6dda828cd79f4164e9a1a838f33b8c36889f0901
3
+ size 636184
build/torch213-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch213-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
20
+ "_metakernel_cuda_4611d12.abi3.so": "l/B+X/EoPLx/cV8bbdqCjNefQWTpoag48zuMNoifCQE=",
21
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
22
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch213-cxx11-cu126-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7b1c2a4f6a13c1ca3458133df0c62730801fb87f38a0eeaa556752c2eb87c25a
3
+ size 967576
build/torch213-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch213-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
22
+ "_metakernel_cuda_4611d12.abi3.so": "exwqT2oTwco0WBM98MYnMIAfuH84oO6qVWdSwuuHwlo=",
23
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
24
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch213-cxx11-cu130-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """metakernel: the measuring instrument for the kernel-design loop.
2
+
3
+ An agent iterating on CUDA candidates is good at writing code and bad at
4
+ trusting its own measurements; it needs trustworthy timing, adversarial
5
+ correctness comparison, and measured facts about the device it targets.
6
+ metakernel ships those as loadable ops:
7
+
8
+ from kernels import get_kernel
9
+ mk = get_kernel("phanerozoic/metakernel", version=1,
10
+ trust_remote_code=True)
11
+
12
+ dossier = mk.probe_device() # measured facts
13
+ report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
14
+ verdict = mk.compare(ref, cand, gen) # ULP evidence
15
+ table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
16
+
17
+ Every op returns plain dicts and tensors, never prose. Every dossier entry
18
+ is produced by a dedicated probe kernel rather than a spec sheet, carries
19
+ the SM clock it was measured under, and labels throttled samples instead
20
+ of letting them poison medians. No hardware counters are used: everything
21
+ is inferred from timing, clocks, and the occupancy API, so entries carry a
22
+ confidence field (timing-differential inference can misattribute).
23
+ """
24
+ import math
25
+ import time
26
+
27
+ import torch
28
+
29
+ from ._ops import ops
30
+
31
+ __all__ = ["probe_device", "bench", "compare", "order_shuffles",
32
+ "fuzz_shapes", "sweep", "alloc_stamps", "read_stamps",
33
+ "measure_clock", "ulp_diff", "ops"]
34
+
35
+ _MMA_KINDS = {
36
+ "fp16": (0, 16 * 16 * 16),
37
+ "bf16": (1, 16 * 16 * 16),
38
+ "tf32": (2, 16 * 16 * 8),
39
+ "int8": (3, 16 * 16 * 16),
40
+ "fp8_e4m3": (4, 16 * 8 * 32),
41
+ }
42
+
43
+
44
+ def _sync():
45
+ torch.cuda.synchronize()
46
+
47
+
48
+ def _events(fn, reps):
49
+ """Time reps calls of fn with one event pair each; returns ms list."""
50
+ starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
51
+ stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
52
+ for s, e in zip(starts, stops):
53
+ s.record()
54
+ fn()
55
+ e.record()
56
+ _sync()
57
+ return [s.elapsed_time(e) for s, e in zip(starts, stops)]
58
+
59
+
60
+ def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
61
+ """Effective SM clock in GHz under an FMA spin of `ticks` clock64 ticks.
62
+ Wall time over elapsed ticks is the frequency the ticks ran at."""
63
+ props = torch.cuda.get_device_properties(device)
64
+ if blocks is None:
65
+ blocks = props.multi_processor_count
66
+ out = torch.zeros(2, dtype=torch.int64, device=device)
67
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
68
+ ops.mk_spin(blocks, 256, ticks, out, sink) # warm
69
+ start = torch.cuda.Event(enable_timing=True)
70
+ stop = torch.cuda.Event(enable_timing=True)
71
+ start.record()
72
+ ops.mk_spin(blocks, 256, ticks, out, sink)
73
+ stop.record()
74
+ _sync()
75
+ ms = start.elapsed_time(stop)
76
+ elapsed = int(out[0].item())
77
+ return elapsed / (ms * 1e6)
78
+
79
+
80
+ def _median(xs):
81
+ s = sorted(xs)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else 0.5 * (s[n // 2 - 1] + s[n // 2])
84
+
85
+
86
+ def _entry(value, units, method, clock_ghz, confidence, note=""):
87
+ d = {"value": value, "units": units, "method": method,
88
+ "clock_ghz": round(clock_ghz, 3), "confidence": confidence}
89
+ if note:
90
+ d["note"] = note
91
+ return d
92
+
93
+
94
+ def _sattolo_ring(n, device, generator=None):
95
+ """A random single-cycle permutation as an int32 next-pointer table, so
96
+ a pointer chase visits every slot before repeating."""
97
+ perm = torch.randperm(n, generator=generator)
98
+ ring = torch.empty(n, dtype=torch.int32)
99
+ ring[perm] = torch.roll(perm, -1).to(torch.int32)
100
+ return ring.to(device)
101
+
102
+
103
+ def probe_device(device="cuda", quick=False):
104
+ """Empirical device dossier. Each entry is measured by a dedicated
105
+ probe kernel and carries units, method, the SM clock it ran under, and
106
+ a confidence field."""
107
+ props = torch.cuda.get_device_properties(device)
108
+ sm = props.multi_processor_count
109
+ l2 = props.L2_cache_size
110
+ d = {
111
+ "device": {
112
+ "name": props.name,
113
+ "sm_arch": f"sm{props.major}{props.minor}",
114
+ "sms": sm,
115
+ "l2_bytes": l2,
116
+ "vram_bytes": props.total_memory,
117
+ "torch": torch.__version__,
118
+ "cuda_runtime": torch.version.cuda,
119
+ }
120
+ }
121
+ sink = torch.zeros(2, dtype=torch.float32, device=device)
122
+ out64 = torch.zeros(2, dtype=torch.int64, device=device)
123
+ reps = 3 if quick else 5
124
+
125
+ # ---- clock under sustained load
126
+ traj = []
127
+ windows = 8 if quick else 24
128
+ for _ in range(windows):
129
+ traj.append(measure_clock(device, ticks=30_000_000))
130
+ d["clock"] = {
131
+ "max_ghz": round(max(traj), 3),
132
+ "min_ghz": round(min(traj), 3),
133
+ "final_ghz": round(traj[-1], 3),
134
+ "sag_pct": round(100 * (1 - traj[-1] / max(traj)), 1),
135
+ "units": "GHz",
136
+ "method": f"{windows} back-to-back FMA spin windows of 30M ticks; "
137
+ "wall/ticks per window",
138
+ "confidence": "high",
139
+ }
140
+ clk = max(traj)
141
+
142
+ # ---- HBM bandwidth: triad per access width, plus read-only,
143
+ # write-only, and random-gather
144
+ n4 = 16 << 20 # 16M float4 = 256 MB per buffer
145
+ if props.total_memory < 4 << 30:
146
+ n4 = 4 << 20
147
+ a = torch.rand(n4 * 4, device=device)
148
+ b = torch.rand(n4 * 4, device=device)
149
+ c = torch.empty(n4 * 4, device=device)
150
+ hbm = {}
151
+ for width in (1, 2, 4):
152
+ ops.mk_triad(a, b, c, width, 1.5) # warm
153
+ ms = _median(_events(lambda w=width: ops.mk_triad(a, b, c, w, 1.5),
154
+ reps))
155
+ gbs = 3 * a.numel() * 4 / (ms * 1e-3) / 1e9
156
+ hbm[f"triad_{4 * width}B"] = round(gbs, 1)
157
+ ops.mk_read(a, sink)
158
+ ms = _median(_events(lambda: ops.mk_read(a, sink), reps))
159
+ hbm["read_only"] = round(a.numel() * 4 / (ms * 1e-3) / 1e9, 1)
160
+ ops.mk_write(c, 1.0)
161
+ ms = _median(_events(lambda: ops.mk_write(c, 1.0), reps))
162
+ hbm["write_only"] = round(c.numel() * 4 / (ms * 1e-3) / 1e9, 1)
163
+ g = torch.Generator(device="cpu").manual_seed(7)
164
+ idx = torch.randint(0, n4, (n4,), generator=g,
165
+ dtype=torch.int32).to(device)
166
+ ops.mk_gather(a, idx, c, 256, 0)
167
+ ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
168
+ # traffic accounting: idx reads + 16B random loads + streamed stores
169
+ hbm["random_gather_16B"] = round(
170
+ (idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
171
+ d["hbm_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
172
+ "width; read-only fold; write-only fill; random "
173
+ "float4 gather (16B granules, nominal traffic)",
174
+ clk, "high",
175
+ "gather moves full cache lines per 16B granule, "
176
+ "so its nominal number is the achievable rate for "
177
+ "random access, not a line-efficiency claim")
178
+
179
+ # ---- L2 bandwidth (working set 3 buffers inside L2)
180
+ ws = max(l2 // 4, 1 << 20)
181
+ nl2 = ws // 16 # float4 elems per buffer
182
+ al = a[:nl2 * 4].contiguous()
183
+ bl = b[:nl2 * 4].contiguous()
184
+ cl = c[:nl2 * 4].contiguous()
185
+ passes = 64
186
+ ops.mk_triad_passes(al, bl, cl, 1.5, passes)
187
+ ms = _median(_events(
188
+ lambda: ops.mk_triad_passes(al, bl, cl, 1.5, passes), reps))
189
+ gbs = 3 * al.numel() * 4 * passes / (ms * 1e-3) / 1e9
190
+ d["l2_bw"] = _entry(round(gbs, 1), "GB/s",
191
+ f"in-kernel repeated triad passes over "
192
+ f"{3 * ws >> 20} MB resident working set", clk,
193
+ "medium", "first pass streams from HBM; amortized "
194
+ f"over {passes} passes")
195
+
196
+ # ---- latencies: pointer chase per level. Footprints are chosen so
197
+ # the chain hits the intended level: the L2 ring (2 MB) exceeds L1,
198
+ # fits L2 on every supported part, and spans few enough pages to stay
199
+ # TLB-resident after a full-cycle warm walk; the HBM chain is long
200
+ # enough (2x L2 in touched lines) that it evicts itself and cannot be
201
+ # served from cache.
202
+ lat = {}
203
+ idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
204
+ ring_s = _sattolo_ring(8192, device)
205
+ ops.mk_chase_shared(ring_s, 1 << 14, out64)
206
+ _sync()
207
+ hops = 1 << 16
208
+ ops.mk_chase_shared(ring_s, hops, out64)
209
+ _sync()
210
+ lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
211
+
212
+ l1_ring_n = (16 << 10) // 4 # 16 KB: L1-resident after a warm walk
213
+ ring_l1 = _sattolo_ring(l1_ring_n, device)
214
+ ops.mk_chase_global(ring_l1, l1_ring_n, out64)
215
+ _sync()
216
+ hops_l1 = 1 << 15
217
+ ops.mk_chase_global(ring_l1, hops_l1, out64)
218
+ _sync()
219
+ lat["l1_cycles"] = round(int(out64[0].item()) / hops_l1, 1)
220
+
221
+ l2_ring_n = (2 << 20) // 4
222
+ ring_l2 = _sattolo_ring(l2_ring_n, device)
223
+ ops.mk_chase_global(ring_l2, l2_ring_n, out64) # full-cycle warm walk
224
+ _sync()
225
+ hops_l2 = 1 << 14
226
+ ops.mk_chase_global(ring_l2, hops_l2, out64)
227
+ _sync()
228
+ lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
229
+
230
+ hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
231
+ ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
232
+ hops_h = max((2 * l2) // 128, 1 << 17)
233
+ ops.mk_chase_global(ring_h, 1 << 14, out64) # fault pages / warm TLB
234
+ _sync()
235
+ ops.mk_chase_global(ring_h, hops_h, out64)
236
+ _sync()
237
+ lat["hbm_cycles"] = round(int(out64[0].item()) / hops_h, 1)
238
+ lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
239
+ d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
240
+ "Sattolo-cycle pointer chase; 16 KB ring for L1, "
241
+ "2 MB TLB-resident ring for L2, self-evicting "
242
+ "chain (2x L2 in touched lines) for HBM",
243
+ idle_clk, "high",
244
+ "smem includes index arithmetic; HBM includes "
245
+ "residual TLB effects at large footprints")
246
+ del ring_h, ring_l2, ring_l1, ring_s
247
+
248
+ # ---- fp32 FMA and tensor-core throughput
249
+ blocks = sm * 4
250
+ iters = 20_000 if quick else 60_000
251
+ ops.mk_fma_f32(blocks, 256, iters, sink)
252
+ ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
253
+ reps))
254
+ flops = blocks * 256 * iters * 16 * 2 * 2 # 2 chains x 2 flops x unroll
255
+ d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
256
+ "register-resident FMA chains", clk, "high")
257
+ sink64 = torch.zeros(2, dtype=torch.float64, device=device)
258
+ it64 = max(iters // 16, 1000)
259
+ ops.mk_fma_f64(blocks, 256, it64, sink64)
260
+ ms = _median(_events(
261
+ lambda: ops.mk_fma_f64(blocks, 256, it64, sink64), reps))
262
+ flops64 = blocks * 256 * it64 * 16 * 2 * 2
263
+ d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
264
+ "register-resident double FMA chains (the DFMA "
265
+ "pipe)", clk, "high")
266
+ mma = {}
267
+ for name, (kind, mnk) in _MMA_KINDS.items():
268
+ it = 30_000 if quick else 100_000
269
+ r = ops.mk_mma(kind, blocks, 8, 512, sink)
270
+ if r != 0:
271
+ mma[name] = "unsupported"
272
+ continue
273
+ _sync()
274
+ ms = _median(_events(
275
+ lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
276
+ flops = blocks * 8 * it * 4 * 2 * mnk
277
+ mma[name] = round(flops / (ms * 1e-3) / 1e12, 1)
278
+ d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
279
+ "register-fragment mma_sync loops (wmma; fp8 via "
280
+ "inline PTX m16n8k32)", clk, "medium",
281
+ "dense-tile rate without memory traffic; real "
282
+ "kernels see this only when staging hides behind it")
283
+
284
+ # ---- atomic contention curve: throughput vs number of distinct
285
+ # addresses the whole grid hammers (slots = 1 is full contention;
286
+ # slots = threads is contention-free), plus the shared-memory point
287
+ n_thr = blocks * 256
288
+ buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
289
+ at = {"curve_slots": {}}
290
+ it_at = 2_000 if quick else 6_000
291
+ slot_points = [1, 8, 64, 512, 4096, n_thr]
292
+ for slots in slot_points:
293
+ buf.zero_()
294
+ ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
295
+ ms = _median(_events(
296
+ lambda s=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s),
297
+ reps))
298
+ at["curve_slots"][str(slots)] = round(
299
+ n_thr * it_at / (ms * 1e-3) / 1e9, 2)
300
+ ops.mk_atomics(3, blocks, 256, it_at, buf, 1)
301
+ ms = _median(_events(
302
+ lambda: ops.mk_atomics(3, blocks, 256, it_at, buf, 1), reps))
303
+ at["shared_same_addr"] = round(n_thr * it_at / (ms * 1e-3) / 1e9, 2)
304
+ d["atomics_f32"] = _entry(at, "Gop/s",
305
+ "atomicAdd hammer; curve sweeps the number "
306
+ "of distinct global addresses from 1 (full "
307
+ "contention) to one per thread", clk, "high")
308
+
309
+ # ---- launch overhead
310
+ anchor = sink
311
+ n_launch = 500 if quick else 2000
312
+ _sync()
313
+ t0 = time.perf_counter()
314
+ for _ in range(n_launch):
315
+ ops.mk_empty(anchor)
316
+ _sync()
317
+ queued = (time.perf_counter() - t0) / n_launch * 1e6
318
+ n_rt = 100 if quick else 300
319
+ t0 = time.perf_counter()
320
+ for _ in range(n_rt):
321
+ ops.mk_empty(anchor)
322
+ _sync()
323
+ rt = (time.perf_counter() - t0) / n_rt * 1e6
324
+ d["launch_overhead"] = _entry(
325
+ {"queued_us": round(queued, 2), "round_trip_us": round(rt, 2)},
326
+ "us", "empty-kernel trains, queued vs launch+sync", clk, "high",
327
+ "host-dependent: includes torch op dispatch")
328
+
329
+ # ---- grid barrier
330
+ bar_iters = 500 if quick else 2000
331
+ r = ops.mk_barrier(sm, 256, bar_iters, out64)
332
+ if r == 0:
333
+ _sync()
334
+ ticks = int(out64[0].item())
335
+ d["grid_barrier"] = _entry(
336
+ round(ticks / bar_iters / clk / 1000.0, 2), "us per grid.sync",
337
+ f"cooperative kernel, {sm} blocks x 256 threads", clk, "high")
338
+ else:
339
+ d["grid_barrier"] = _entry("unsupported", "", "cooperative launch "
340
+ "unavailable or grid too large", clk,
341
+ "high")
342
+
343
+ # ---- occupancy table (API) plus timed knees: the same random gather
344
+ # at every (threads, dyn_smem) point; when the forced blocks/SM stop
345
+ # covering memory latency, achieved bandwidth falls off the cliff
346
+ occ = {}
347
+ for threads in (128, 256, 512, 1024):
348
+ row = {}
349
+ for smem_kb in (0, 16, 32, 48):
350
+ row[f"{smem_kb}KB"] = int(ops.mk_occupancy(
351
+ threads, smem_kb * 1024, anchor))
352
+ occ[f"t{threads}"] = row
353
+ d["occupancy_blocks_per_sm"] = _entry(
354
+ occ, "blocks/SM", "occupancy API on the float4 triad kernel",
355
+ clk, "high", "API-derived, not timing-derived")
356
+
357
+ knee_iters = 6_000 if quick else 20_000
358
+ knee_blocks = sm * 8
359
+ knees = {}
360
+ for threads in (128, 256, 1024):
361
+ row = {}
362
+ for smem_kb in (0, 32, 48):
363
+ ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
364
+ knee_iters, sink)
365
+ ms = _median(_events(
366
+ lambda t=threads, s=smem_kb: ops.mk_fma_dep(
367
+ knee_blocks, t, s * 1024, knee_iters, sink), reps))
368
+ fl = knee_blocks * threads * knee_iters * 16 * 2
369
+ row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
370
+ knees[f"t{threads}"] = row
371
+ d["occupancy_knees_fma_tflops"] = _entry(
372
+ knees, "TFLOP/s", "a single dependent FMA chain per thread, timed "
373
+ "at forced (threads, dyn smem) occupancies over fixed total work; "
374
+ "throughput is proportional to resident threads until the pipe "
375
+ "saturates, so the cliff marks where occupancy stops covering "
376
+ "latency", clk, "high",
377
+ "timing-derived counterpart to the API table above")
378
+ return d
379
+
380
+
381
+ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
382
+ dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
383
+ device="cuda"):
384
+ """Median-of-N cudaEvent timing with throttle rejection.
385
+
386
+ The SM clock is probed every `clock_stride` samples; samples inside a
387
+ window whose clock sagged below `clock_guard` x the best observed
388
+ clock are rejected and counted rather than silently averaged. With
389
+ declared `bytes`/`flops` per call, reports achieved GB/s / TFLOP/s;
390
+ with a `dossier`, adds roofline placement against the measured peaks.
391
+
392
+ `graph=True` captures fn() into a CUDA graph and times graph replays,
393
+ stripping Python and torch dispatch from the measurement: the right
394
+ mode for kernels in the few-microsecond range, where dispatch is
395
+ otherwise the thing being measured. Capture requires fn to be
396
+ graph-safe (no sync, no allocation growth, static shapes)."""
397
+ for _ in range(warmup):
398
+ fn()
399
+ _sync()
400
+ if graph:
401
+ g = torch.cuda.CUDAGraph()
402
+ with torch.cuda.graph(g):
403
+ fn()
404
+ fn = g.replay
405
+ for _ in range(warmup):
406
+ fn()
407
+ _sync()
408
+ samples = []
409
+ clocks = []
410
+ stride = clock_stride
411
+ for base in range(0, iters, stride):
412
+ clocks.append(measure_clock(device, ticks=1_500_000))
413
+ n = min(stride, iters - base)
414
+ samples.append((_events(fn, n), len(clocks) - 1))
415
+ clocks.append(measure_clock(device, ticks=1_500_000))
416
+ best_clk = max(clocks)
417
+ kept, rejected = [], 0
418
+ flat = []
419
+ for group, ci in samples:
420
+ window_ok = (clocks[ci] >= clock_guard * best_clk and
421
+ clocks[min(ci + 1, len(clocks) - 1)] >=
422
+ clock_guard * best_clk)
423
+ for ms in group:
424
+ flat.append(ms)
425
+ if window_ok:
426
+ kept.append(ms)
427
+ else:
428
+ rejected += 1
429
+ if not kept:
430
+ kept = flat
431
+ med = _median(kept)
432
+ rep = {
433
+ "median_ms": round(med, 4),
434
+ "min_ms": round(min(kept), 4),
435
+ "mean_ms": round(sum(kept) / len(kept), 4),
436
+ "samples": len(flat),
437
+ "rejected_throttled": rejected,
438
+ "graph_captured": bool(graph),
439
+ "clock_ghz": {"best": round(best_clk, 3),
440
+ "worst": round(min(clocks), 3)},
441
+ }
442
+ if bytes is not None:
443
+ rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
444
+ if flops is not None:
445
+ rep["achieved_tflops"] = round(flops / (med * 1e-3) / 1e12, 2)
446
+ if bytes and flops:
447
+ rep["arithmetic_intensity"] = round(flops / bytes, 2)
448
+ if dossier is not None and bytes and flops:
449
+ bw = max(dossier["hbm_bw"]["value"].values()) * 1e9
450
+ mma_v = dossier["mma"]["value"].get(dtype)
451
+ peak = (mma_v if isinstance(mma_v, (int, float))
452
+ else dossier["fma_f32"]["value"]) * 1e12
453
+ ai = flops / bytes
454
+ roof = min(peak, ai * bw)
455
+ rep["roofline"] = {
456
+ "bound": "memory" if ai * bw < peak else "compute",
457
+ "ridge_flops_per_byte": round(peak / bw, 1),
458
+ "pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
459
+ "peak_dtype": dtype,
460
+ }
461
+ return rep
462
+
463
+
464
+ def _to_ordered_int(t):
465
+ """Map float bit patterns to integers whose difference is the ULP
466
+ distance: positive floats keep their bits, negative floats mirror
467
+ below zero, so the mapping is monotone in the float value."""
468
+ if t.dtype == torch.float32:
469
+ u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
470
+ half = 1 << 31
471
+ elif t.dtype in (torch.float16, torch.bfloat16):
472
+ u = t.view(torch.int16).to(torch.int64) & 0xFFFF
473
+ half = 1 << 15
474
+ elif t.dtype == torch.float64:
475
+ i = t.view(torch.int64)
476
+ return torch.where(i >= 0, i, (-(1 << 63)) - i)
477
+ else:
478
+ raise ValueError(f"unsupported dtype {t.dtype}")
479
+ return torch.where(u < half, u, half - u)
480
+
481
+
482
+ def ulp_diff(a, b):
483
+ """Elementwise ULP distance between same-dtype float tensors. NaNs in
484
+ the same position count as 0; a one-sided NaN counts as 2**62."""
485
+ assert a.dtype == b.dtype and a.shape == b.shape
486
+ ia, ib = _to_ordered_int(a), _to_ordered_int(b)
487
+ d = (ia - ib).abs()
488
+ both_nan = torch.isnan(a) & torch.isnan(b)
489
+ one_nan = torch.isnan(a) ^ torch.isnan(b)
490
+ d = torch.where(both_nan, torch.zeros_like(d), d)
491
+ d = torch.where(one_nan, torch.full_like(d, 1 << 62), d)
492
+ return d
493
+
494
+
495
+ class order_shuffles:
496
+ """Built-in order shuffles for the common contractual-noise axes."""
497
+
498
+ @staticmethod
499
+ def last_dim():
500
+ """Permute the last dim of every tensor arg identically: the
501
+ accumulation order of a last-dim reduction changes, the value
502
+ does not."""
503
+ def shuffle(args, seed):
504
+ g = torch.Generator(device=args[0].device).manual_seed(seed)
505
+ p = torch.randperm(args[0].shape[-1], device=args[0].device,
506
+ generator=g)
507
+ return tuple(x[..., p] for x in args)
508
+ return shuffle
509
+
510
+ @staticmethod
511
+ def matmul_k():
512
+ """Permute K of (A [.., M, K], B [.., K, N]) consistently: the
513
+ product is unchanged, the accumulation order is not."""
514
+ def shuffle(args, seed):
515
+ a_m, b_m = args[0], args[1]
516
+ g = torch.Generator(device=a_m.device).manual_seed(seed)
517
+ p = torch.randperm(a_m.shape[-1], device=a_m.device,
518
+ generator=g)
519
+ return (a_m[..., p], b_m[..., p, :]) + tuple(args[2:])
520
+ return shuffle
521
+
522
+
523
+ def _as_outputs(x):
524
+ return list(x) if isinstance(x, (tuple, list)) else [x]
525
+
526
+
527
+ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
528
+ ulp_tol=None, atol=None, rtol=None, band_margin=4.0, seed=0):
529
+ """Adversarial correctness comparison with ULP evidence.
530
+
531
+ `gen(seed) -> args` builds inputs; `ref(*args)` and `cand(*args)`
532
+ return a tensor or a tuple of tensors (each output is compared and
533
+ the worst governs). If `order_shuffle(args, seed) -> args` is given
534
+ and reorders contractual-noise dimensions (see `order_shuffles` for
535
+ the common cases), the reference is treated as a distribution:
536
+ candidates are judged against the measured reduction-order band
537
+ rather than a point. Verdict, stated in the report: max ULP <=
538
+ band_margin * band + 2 when a band is measured, else max ULP <=
539
+ ulp_tol (default 2); if `atol`/`rtol` are given they are an
540
+ alternative acceptance route, checked as |c - r| <= atol + rtol|r|."""
541
+ worst = {"max_ulp": -1}
542
+ total_hist = {}
543
+ per_trial = []
544
+ max_abs_seen = 0.0
545
+ max_rel_seen = 0.0
546
+ for t in range(trials):
547
+ args = gen(seed + t)
548
+ r_outs = _as_outputs(ref(*args))
549
+ c_outs = _as_outputs(cand(*args))
550
+ assert len(r_outs) == len(c_outs), "output arity mismatch"
551
+ # the order band is POINTWISE: a near-zero reduction output is
552
+ # legitimately thousands of ULP order-sensitive (cancellation)
553
+ # while its neighbors are stable, so each element is judged
554
+ # against its own measured sensitivity, not a global max
555
+ bands = None
556
+ band = 0
557
+ band_score = 0.0
558
+ if order_shuffle is not None:
559
+ bands = [torch.zeros_like(ro, dtype=torch.int64)
560
+ for ro in r_outs]
561
+ for k in range(order_trials):
562
+ r2 = _as_outputs(
563
+ ref(*order_shuffle(args, 1000 + seed + t * 131 + k)))
564
+ for bi, (ro, r2o) in enumerate(zip(r_outs, r2)):
565
+ bands[bi] = torch.maximum(bands[bi],
566
+ ulp_diff(ro, r2o))
567
+ band = max(int(b.max().item()) if b.numel() else 0
568
+ for b in bands)
569
+ max_ulp = 0
570
+ mean_ulp = 0.0
571
+ first_div = -1
572
+ n_el = 0
573
+ nan_r = nan_c = inf_r = inf_c = 0
574
+ for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
575
+ d = ulp_diff(r, c)
576
+ if bands is not None and d.numel():
577
+ ratio = d.double() / (bands[oi].double() + 2.0)
578
+ band_score = max(band_score, float(ratio.max().item()))
579
+ if d.numel():
580
+ max_ulp = max(max_ulp, int(d.max().item()))
581
+ mean_ulp += float(d.double().sum().item())
582
+ n_el += d.numel()
583
+ nz = (d > 0)
584
+ if first_div < 0 and nz.any():
585
+ first_div = int(nz.flatten().nonzero()[0].item())
586
+ if nz.any():
587
+ buckets = torch.clamp(
588
+ torch.log2(d[nz].double()).floor(), 0, 62
589
+ ).to(torch.int64)
590
+ counts = torch.bincount(buckets, minlength=63)
591
+ for bkt in counts.nonzero().flatten().tolist():
592
+ total_hist[bkt] = total_hist.get(bkt, 0) + int(
593
+ counts[bkt].item())
594
+ fin = torch.isfinite(r) & torch.isfinite(c)
595
+ if fin.any():
596
+ diff = (c[fin].double() - r[fin].double()).abs()
597
+ max_abs_seen = max(max_abs_seen,
598
+ float(diff.max().item()))
599
+ rel = diff / r[fin].double().abs().clamp(min=1e-300)
600
+ max_rel_seen = max(max_rel_seen,
601
+ float(rel.max().item()))
602
+ nan_r += int(torch.isnan(r).sum().item())
603
+ nan_c += int(torch.isnan(c).sum().item())
604
+ inf_r += int(torch.isinf(r).sum().item())
605
+ inf_c += int(torch.isinf(c).sum().item())
606
+ trial_rep = {
607
+ "max_ulp": max_ulp,
608
+ "mean_ulp": round(mean_ulp / max(n_el, 1), 3),
609
+ "first_divergent_index": first_div,
610
+ "nan_ref": nan_r, "nan_cand": nan_c,
611
+ "inf_ref": inf_r, "inf_cand": inf_c,
612
+ "order_band_ulp": band,
613
+ "band_score": round(band_score, 2),
614
+ }
615
+ per_trial.append(trial_rep)
616
+ if max_ulp > worst["max_ulp"]:
617
+ worst = trial_rep
618
+ band_max = max(t["order_band_ulp"] for t in per_trial)
619
+ max_seen = max(t["max_ulp"] for t in per_trial)
620
+ if order_shuffle is not None:
621
+ score = max(t["band_score"] for t in per_trial)
622
+ ulp_ok = score <= band_margin
623
+ criterion = (f"pointwise max(ulp / (order_band + 2)) = "
624
+ f"{score:.2f} <= band_margin {band_margin}")
625
+ else:
626
+ tol = ulp_tol if ulp_tol is not None else 2
627
+ ulp_ok = max_seen <= tol
628
+ criterion = f"max_ulp {max_seen} <= ulp_tol {tol}"
629
+ tol_ok = False
630
+ if atol is not None or rtol is not None:
631
+ tol_ok = max_abs_seen <= (atol or 0) + (rtol or 0) * 1.0 or \
632
+ max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
633
+ criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
634
+ f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
635
+ return {
636
+ "pass": bool(ulp_ok or tol_ok),
637
+ "criterion": criterion,
638
+ "worst_trial": worst,
639
+ "max_abs": max_abs_seen,
640
+ "max_rel": max_rel_seen,
641
+ "trials": per_trial,
642
+ "ulp_hist_log2": dict(sorted(total_hist.items())),
643
+ }
644
+
645
+
646
+ _FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
647
+ "overflow_expand")
648
+
649
+
650
+ def _context_alive(device):
651
+ try:
652
+ t = torch.ones(4, device=device)
653
+ torch.cuda.synchronize()
654
+ return bool(t.sum().item() == 4.0)
655
+ except Exception: # noqa: BLE001
656
+ return False
657
+
658
+
659
+ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
660
+ dtypes=(torch.float32,), ulp_tol=64, device="cuda"):
661
+ """The ugly envelope: run op vs ref across edge shapes, dtypes, and
662
+ layout mutations; report ok / mismatch / exception per case.
663
+
664
+ `make(shape, dtype)` (or `make(shape)`) builds a base input, either a
665
+ tensor or a tuple of tensors for multi-argument ops; layout variants
666
+ derive non-contiguous and misaligned views whose CONTENT equals a
667
+ contiguous copy, so `op(*xs)` must match `ref(*xs)` on every variant.
668
+ `overflow_expand` stride-0-expands the input past 2**31 logical
669
+ elements to catch int32 indexing, and is skipped with status
670
+ `skipped_vram` when materializing the output would not fit. After any
671
+ exception the CUDA context is health-checked; if a candidate killed
672
+ it, remaining cases are marked `context_lost` instead of producing
673
+ garbage."""
674
+ if shapes is None:
675
+ shapes = [(0, 8), (1, 1), (1, 17), (3, 63), (17, 129), (128, 128),
676
+ (1023, 5), (2, 4096)]
677
+
678
+ def build(shape, dtype):
679
+ try:
680
+ out = make(tuple(shape), dtype)
681
+ except TypeError:
682
+ out = make(tuple(shape))
683
+ return out if isinstance(out, (tuple, list)) else (out,)
684
+
685
+ def variant_of(x, v):
686
+ if v == "contig":
687
+ return x
688
+ if v == "transposed":
689
+ return x.t() if x.dim() == 2 else x
690
+ if v == "strided_rows":
691
+ return x[::2] if x.shape[0] >= 2 else x
692
+ if v == "offset_slice":
693
+ # a storage-offset-1 view: misaligned for vectorized loads
694
+ return x.flatten()[1:] if x.numel() > 1 else x
695
+ if v == "overflow_expand":
696
+ if x.numel() == 0:
697
+ return None
698
+ per = max(x.numel(), 1)
699
+ rep = (1 << 31) // per + 2
700
+ return x.reshape((1,) + tuple(x.shape)).expand(
701
+ (rep,) + tuple(x.shape))
702
+ return x
703
+
704
+ rows = []
705
+ lost = False
706
+ for dtype in dtypes:
707
+ for shape in shapes:
708
+ bases = build(shape, dtype)
709
+ for v in variants:
710
+ case = {"shape": tuple(shape), "variant": v,
711
+ "dtype": str(dtype).replace("torch.", "")}
712
+ if lost:
713
+ case["status"] = "context_lost"
714
+ rows.append(case)
715
+ continue
716
+ xs = [variant_of(b, v) for b in bases]
717
+ if any(x is None for x in xs):
718
+ case["status"] = "skipped_empty"
719
+ rows.append(case)
720
+ continue
721
+ if v == "overflow_expand":
722
+ logical = max(x.numel() for x in xs)
723
+ need = logical * xs[0].element_size() * 2
724
+ free, _total = torch.cuda.mem_get_info()
725
+ if need > free * 0.8:
726
+ case["status"] = "skipped_vram"
727
+ case["logical_elems"] = int(logical)
728
+ rows.append(case)
729
+ continue
730
+ case["logical_elems"] = int(logical)
731
+ case["contiguous"] = bool(xs[0].is_contiguous())
732
+ try:
733
+ got = op(*xs)
734
+ want = ref(*xs)
735
+ if v == "overflow_expand":
736
+ # bounded comparison: the head plus the high-index
737
+ # tail, which is where int32 flat indexing wraps
738
+ w = 1 << 20
739
+ gf, wf = got.flatten(), want.flatten()
740
+ pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
741
+ else:
742
+ pairs = ((got, want),)
743
+ mx = 0
744
+ for gp, wp in pairs:
745
+ d = ulp_diff(wp, gp) if gp.is_floating_point() \
746
+ else (wp != gp).to(torch.int64) * (1 << 62)
747
+ if d.numel():
748
+ mx = max(mx, int(d.max().item()))
749
+ del d
750
+ case["status"] = "ok" if mx <= ulp_tol else "mismatch"
751
+ case["max_ulp"] = mx
752
+ del got, want
753
+ except Exception as e: # noqa: BLE001 - report, don't die
754
+ case["status"] = "exception"
755
+ case["error"] = f"{type(e).__name__}: {str(e)[:120]}"
756
+ if not _context_alive(device):
757
+ lost = True
758
+ case["status"] = "context_killed"
759
+ rows.append(case)
760
+ n_bad = sum(1 for r in rows
761
+ if r["status"] not in ("ok", "skipped_vram",
762
+ "skipped_empty"))
763
+ return {"cases": rows, "failed": n_bad, "total": len(rows),
764
+ "context_lost": lost}
765
+
766
+
767
+ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
768
+ check=None, budget_s=None):
769
+ """Launch-configuration search: `factory(**point) -> callable`; one
770
+ bench row per grid point, sorted fastest first. A point whose factory
771
+ or run raises is reported, not skipped silently. `check(fn) -> bool`
772
+ is an optional correctness gate recorded per row (a fast wrong config
773
+ must not win a sweep). `budget_s` caps total sweep time; points not
774
+ reached are reported as `not_run` rather than silently dropped."""
775
+ keys = list(grid.keys())
776
+
777
+ def points(i, acc):
778
+ if i == len(keys):
779
+ yield dict(acc)
780
+ return
781
+ for v in grid[keys[i]]:
782
+ acc[keys[i]] = v
783
+ yield from points(i + 1, acc)
784
+
785
+ rows = []
786
+ t_start = time.perf_counter()
787
+ over_budget = False
788
+ for p in points(0, {}):
789
+ row = dict(p)
790
+ if over_budget or (budget_s is not None and
791
+ time.perf_counter() - t_start > budget_s):
792
+ over_budget = True
793
+ row["status"] = "not_run"
794
+ row["median_ms"] = float("inf")
795
+ rows.append(row)
796
+ continue
797
+ try:
798
+ fn = factory(**p)
799
+ if check is not None:
800
+ row["check"] = bool(check(fn))
801
+ rep = bench(fn, iters=iters, warmup=warmup, bytes=bytes,
802
+ flops=flops)
803
+ row.update({"median_ms": rep["median_ms"],
804
+ "rejected_throttled": rep["rejected_throttled"]})
805
+ if "achieved_gbs" in rep:
806
+ row["achieved_gbs"] = rep["achieved_gbs"]
807
+ if "achieved_tflops" in rep:
808
+ row["achieved_tflops"] = rep["achieved_tflops"]
809
+ except Exception as e: # noqa: BLE001
810
+ row["error"] = f"{type(e).__name__}: {str(e)[:120]}"
811
+ row["median_ms"] = float("inf")
812
+ rows.append(row)
813
+ rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
814
+ return rows
815
+
816
+
817
+ def alloc_stamps(n, device="cuda"):
818
+ """Device buffer for the MK_STAMP convention (metakernel_cuda/
819
+ stamps.cuh): the candidate kernel writes clock64() into slot i."""
820
+ return torch.zeros(n, dtype=torch.int64, device=device)
821
+
822
+
823
+ def read_stamps(stamps, labels=None, clock_ghz=None):
824
+ """Decode MK_STAMP phases: consecutive deltas in ticks and in
825
+ microseconds under the measured (or provided) SM clock. A 1-D buffer
826
+ (MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
827
+ buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
828
+ phase, with blocks whose stamps stayed zero excluded."""
829
+ if clock_ghz is None:
830
+ clock_ghz = measure_clock(stamps.device)
831
+
832
+ def us(ticks):
833
+ return round(ticks / clock_ghz / 1000.0, 3)
834
+
835
+ if stamps.dim() == 1:
836
+ vals = stamps.cpu().tolist()
837
+ phases = []
838
+ for i in range(len(vals) - 1):
839
+ ticks = vals[i + 1] - vals[i]
840
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
841
+ phases.append({"phase": name, "ticks": ticks, "us": us(ticks)})
842
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3)}
843
+
844
+ m = stamps.cpu()
845
+ live = (m != 0).all(dim=1)
846
+ m = m[live]
847
+ deltas = (m[:, 1:] - m[:, :-1]).to(torch.float64)
848
+ phases = []
849
+ for i in range(deltas.shape[1]):
850
+ col = deltas[:, i]
851
+ name = labels[i] if labels and i < len(labels) else f"phase_{i}"
852
+ med = float(col.median().item()) if col.numel() else 0.0
853
+ phases.append({
854
+ "phase": name,
855
+ "ticks_median": int(med),
856
+ "ticks_min": int(col.min().item()) if col.numel() else 0,
857
+ "ticks_max": int(col.max().item()) if col.numel() else 0,
858
+ "us_median": us(med),
859
+ "blocks": int(col.numel()),
860
+ })
861
+ return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
862
+ "blocks_reporting": int(live.sum().item()),
863
+ "blocks_total": int(live.numel())}
build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_4611d12.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5c655a858d5a27e42fbfc363c51f5e84f52df5ef7c4be8ad292cc91e4b5f9865
3
+ size 980192
build/torch213-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _metakernel_cuda_4611d12
3
+ ops = torch.ops._metakernel_cuda_4611d12
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_metakernel_cuda_4611d12::{op_name}"
build/torch213-cxx11-cu132-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "metakernel",
3
+ "id": "_metakernel_cuda_4611d12",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "cZxKX5I/2EKCMC2gdCwflUsZaQJWJesmAHq2w+fVjXo=",
22
+ "_metakernel_cuda_4611d12.abi3.so": "XGVahY1aJ+Qvv8NjxR9ehPUt9e98S+itKSzJHktfmGU=",
23
+ "_ops.py": "uZlR720EcTy8M7BCydmvaZAlKonkwTU3778nhR9yQOE=",
24
+ "metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "4611d1213a76a9ec1d12646553a528feb60394ff",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch213-cxx11-cu132-x86_64-linux/metakernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))