Uploaded using `kernel-builder`.
Browse files- .gitattributes +6 -0
- build/torch212-cxx11-cu126-x86_64-linux/__init__.py +935 -300
- build/torch212-cxx11-cu126-x86_64-linux/{_metakernel_cuda_b62a1ce.abi3.so → _metakernel_cuda_0b9de42.abi3.so} +2 -2
- build/torch212-cxx11-cu126-x86_64-linux/_ops.py +3 -3
- build/torch212-cxx11-cu126-x86_64-linux/_pinned.py +194 -102
- build/torch212-cxx11-cu126-x86_64-linux/metadata.json +8 -8
- build/torch212-cxx11-cu126-x86_64-linux/metakernel/__init__.py +0 -26
- build/torch212-cxx11-cu130-x86_64-linux/__init__.py +935 -300
- build/{torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so → torch212-cxx11-cu130-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so} +2 -2
- build/torch212-cxx11-cu130-x86_64-linux/_ops.py +3 -3
- build/torch212-cxx11-cu130-x86_64-linux/_pinned.py +194 -102
- build/torch212-cxx11-cu130-x86_64-linux/metadata.json +8 -8
- build/torch212-cxx11-cu130-x86_64-linux/metakernel/__init__.py +0 -26
- build/torch212-cxx11-cu132-x86_64-linux/__init__.py +935 -300
- build/{torch212-cxx11-cu130-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so → torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so} +2 -2
- build/torch212-cxx11-cu132-x86_64-linux/_ops.py +3 -3
- build/torch212-cxx11-cu132-x86_64-linux/_pinned.py +194 -102
- build/torch212-cxx11-cu132-x86_64-linux/metadata.json +8 -8
- build/torch212-cxx11-cu132-x86_64-linux/metakernel/__init__.py +0 -26
- build/torch213-cxx11-cu126-x86_64-linux/__init__.py +935 -300
- build/torch213-cxx11-cu126-x86_64-linux/{_metakernel_cuda_b62a1ce.abi3.so → _metakernel_cuda_0b9de42.abi3.so} +2 -2
- build/torch213-cxx11-cu126-x86_64-linux/_ops.py +3 -3
- build/torch213-cxx11-cu126-x86_64-linux/_pinned.py +194 -102
- build/torch213-cxx11-cu126-x86_64-linux/metadata.json +8 -8
- build/torch213-cxx11-cu126-x86_64-linux/metakernel/__init__.py +0 -26
- build/torch213-cxx11-cu130-x86_64-linux/__init__.py +935 -300
- build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so +3 -0
- build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so +0 -3
- build/torch213-cxx11-cu130-x86_64-linux/_ops.py +3 -3
- build/torch213-cxx11-cu130-x86_64-linux/_pinned.py +194 -102
- build/torch213-cxx11-cu130-x86_64-linux/metadata.json +8 -8
- build/torch213-cxx11-cu130-x86_64-linux/metakernel/__init__.py +0 -26
- build/torch213-cxx11-cu132-x86_64-linux/__init__.py +935 -300
- build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so +3 -0
- build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so +0 -3
- build/torch213-cxx11-cu132-x86_64-linux/_ops.py +3 -3
- build/torch213-cxx11-cu132-x86_64-linux/_pinned.py +194 -102
- build/torch213-cxx11-cu132-x86_64-linux/metadata.json +8 -8
- build/torch213-cxx11-cu132-x86_64-linux/metakernel/__init__.py +0 -26
.gitattributes
CHANGED
|
@@ -96,3 +96,9 @@ build/torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so filter=
|
|
| 96 |
build/torch213-cxx11-cu126-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 97 |
build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 98 |
build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
build/torch213-cxx11-cu126-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 97 |
build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 98 |
build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 99 |
+
build/torch212-cxx11-cu126-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 100 |
+
build/torch212-cxx11-cu130-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 101 |
+
build/torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 102 |
+
build/torch213-cxx11-cu126-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 103 |
+
build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 104 |
+
build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so filter=lfs diff=lfs merge=lfs -text
|
build/torch212-cxx11-cu126-x86_64-linux/__init__.py
CHANGED
|
@@ -1,20 +1,4 @@
|
|
| 1 |
-
"""Measuring instrument for the kernel-design loop: timing, adversarial
|
| 2 |
-
correctness comparison, and measured device facts, as loadable ops.
|
| 3 |
-
|
| 4 |
-
from kernels import get_kernel
|
| 5 |
-
mk = get_kernel("phanerozoic/metakernel", version=1,
|
| 6 |
-
trust_remote_code=True)
|
| 7 |
-
|
| 8 |
-
dossier = mk.probe_device() # measured facts
|
| 9 |
-
report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
|
| 10 |
-
verdict = mk.compare(ref, cand, gen) # ULP evidence
|
| 11 |
-
table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
|
| 12 |
-
|
| 13 |
-
Every op returns plain dicts and tensors. Every dossier entry is
|
| 14 |
-
measured by a dedicated probe kernel, carries its SM clock, and labels
|
| 15 |
-
throttled samples; no hardware counters, so entries carry a confidence
|
| 16 |
-
field.
|
| 17 |
-
"""
|
| 18 |
import math
|
| 19 |
import os
|
| 20 |
import time
|
|
@@ -24,9 +8,7 @@ import torch
|
|
| 24 |
try:
|
| 25 |
from ._ops import ops
|
| 26 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 27 |
-
#
|
| 28 |
-
# checkout there is no generated _ops module, so rebuild the op table
|
| 29 |
-
# from the (cached) JIT extension that load_local advertised.
|
| 30 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 31 |
if not _root:
|
| 32 |
raise
|
|
@@ -48,24 +30,25 @@ try:
|
|
| 48 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 49 |
_PINNED = {}
|
| 50 |
|
| 51 |
-
__all__ = ["probe_device", "bench", "compare", "
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"pinned_dossiers", "
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
#
|
| 62 |
_MMA_KINDS = {
|
| 63 |
-
"fp16": (0, 16 * 16, 16,
|
| 64 |
-
"bf16": (1, 16 * 16, 16,
|
| 65 |
-
"tf32": (2, 16 * 16, 8,
|
| 66 |
-
"int8": (3, 16 * 16, 16,
|
| 67 |
-
"fp8_e4m3": (4, 16 * 8, 32,
|
| 68 |
-
"fp16_sparse_2_4": (5, 16 * 8, 32,
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
|
|
@@ -74,7 +57,7 @@ def _sync():
|
|
| 74 |
|
| 75 |
|
| 76 |
def _events(fn, reps):
|
| 77 |
-
"""Time reps calls of fn with one
|
| 78 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 79 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 80 |
for s, e in zip(starts, stops):
|
|
@@ -86,8 +69,7 @@ def _events(fn, reps):
|
|
| 86 |
|
| 87 |
|
| 88 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 89 |
-
"""Effective SM clock in GHz
|
| 90 |
-
Wall time over elapsed ticks is the frequency the ticks ran at."""
|
| 91 |
props = torch.cuda.get_device_properties(device)
|
| 92 |
if blocks is None:
|
| 93 |
blocks = props.multi_processor_count
|
|
@@ -120,8 +102,7 @@ def _entry(value, units, method, clock_ghz, confidence, note=""):
|
|
| 120 |
|
| 121 |
|
| 122 |
def _sattolo_ring(n, device, generator=None):
|
| 123 |
-
"""
|
| 124 |
-
a pointer chase visits every slot before repeating."""
|
| 125 |
perm = torch.randperm(n, generator=generator)
|
| 126 |
ring = torch.empty(n, dtype=torch.int32)
|
| 127 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
@@ -129,8 +110,7 @@ def _sattolo_ring(n, device, generator=None):
|
|
| 129 |
|
| 130 |
|
| 131 |
def _smi_settings(index):
|
| 132 |
-
"""ECC mode, driver,
|
| 133 |
-
when the tool is unavailable."""
|
| 134 |
import subprocess
|
| 135 |
try:
|
| 136 |
out = subprocess.run(
|
|
@@ -150,10 +130,19 @@ def _smi_settings(index):
|
|
| 150 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 151 |
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def mma_tile_check(device="cuda", trials=8):
|
| 154 |
-
"""Random small-integer m16n16k16 tiles through the wmma
|
| 155 |
-
path, compared exactly to the reference GEMM; pins M, N, K, and
|
| 156 |
-
operand mapping (integer operands make fp32 accumulation exact)."""
|
| 157 |
worst = 0.0
|
| 158 |
for t in range(trials):
|
| 159 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
@@ -171,10 +160,83 @@ def mma_tile_check(device="cuda", trials=8):
|
|
| 171 |
"trials": trials}
|
| 172 |
|
| 173 |
|
| 174 |
-
def
|
| 175 |
-
"""
|
| 176 |
-
|
| 177 |
-
a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
props = torch.cuda.get_device_properties(device)
|
| 179 |
sm = props.multi_processor_count
|
| 180 |
l2 = props.L2_cache_size
|
|
@@ -187,8 +249,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 187 |
"vram_bytes": props.total_memory,
|
| 188 |
"torch": torch.__version__,
|
| 189 |
"cuda_runtime": torch.version.cuda,
|
| 190 |
-
# instrument settings
|
| 191 |
-
# dossier records them (None when nvidia-smi is unavailable)
|
| 192 |
**_smi_settings(torch.device(device).index or 0),
|
| 193 |
}
|
| 194 |
}
|
|
@@ -213,9 +274,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 213 |
}
|
| 214 |
clk = max(traj)
|
| 215 |
|
| 216 |
-
# ---- device-memory bandwidth
|
| 217 |
-
|
| 218 |
-
n4 = 16 << 20 # 16M float4 = 256 MB per buffer
|
| 219 |
if props.total_memory < 4 << 30:
|
| 220 |
n4 = 4 << 20
|
| 221 |
a = torch.rand(n4 * 4, device=device)
|
|
@@ -239,7 +299,6 @@ def probe_device(device="cuda", quick=False):
|
|
| 239 |
dtype=torch.int32).to(device)
|
| 240 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 241 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
| 242 |
-
# traffic accounting: idx reads + 16B random loads + streamed stores
|
| 243 |
hbm["random_gather_16B"] = round(
|
| 244 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 245 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
@@ -250,9 +309,9 @@ def probe_device(device="cuda", quick=False):
|
|
| 250 |
"so its nominal number is the achievable rate for "
|
| 251 |
"random access, not a line-efficiency claim")
|
| 252 |
|
| 253 |
-
# ---- L2 bandwidth
|
| 254 |
ws = max(l2 // 4, 1 << 20)
|
| 255 |
-
nl2 = ws // 16
|
| 256 |
al = a[:nl2 * 4].contiguous()
|
| 257 |
bl = b[:nl2 * 4].contiguous()
|
| 258 |
cl = c[:nl2 * 4].contiguous()
|
|
@@ -267,62 +326,61 @@ def probe_device(device="cuda", quick=False):
|
|
| 267 |
"medium", "first pass streams from HBM; amortized "
|
| 268 |
f"over {passes} passes")
|
| 269 |
|
| 270 |
-
# ---- latencies:
|
| 271 |
-
# L1, fits L2 everywhere, and stays TLB-resident after a warm walk;
|
| 272 |
-
# the DRAM chain touches 2x L2 in lines, so it evicts itself.
|
| 273 |
lat = {}
|
| 274 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
| 275 |
-
ring_s = _sattolo_ring(8192, device)
|
| 276 |
-
ops.mk_chase_shared(ring_s, 1 << 14, out64)
|
| 277 |
-
_sync()
|
| 278 |
-
hops = 1 << 16
|
| 279 |
-
ops.mk_chase_shared(ring_s, hops, out64)
|
| 280 |
-
_sync()
|
| 281 |
-
lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
_sync()
|
| 299 |
-
lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 302 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 303 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
_sync()
|
| 308 |
-
lat["dram_cycles"] = round(int(out64[0].item()) / hops_h, 1)
|
| 309 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 310 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 311 |
-
"Sattolo-cycle pointer chase
|
| 312 |
-
"2 MB TLB-resident ring for L2,
|
| 313 |
-
"chain (2x L2 in touched lines)
|
| 314 |
-
idle_clk, "high",
|
| 315 |
"smem includes index arithmetic; HBM includes "
|
| 316 |
"residual TLB effects at large footprints")
|
| 317 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 318 |
|
| 319 |
-
# ---- fp32 FMA and tensor-core throughput
|
| 320 |
blocks = sm * 4
|
| 321 |
iters = 20_000 if quick else 60_000
|
| 322 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 323 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 324 |
reps))
|
| 325 |
-
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 326 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 327 |
"register-resident FMA chains", clk, "high")
|
| 328 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
@@ -334,43 +392,88 @@ def probe_device(device="cuda", quick=False):
|
|
| 334 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 335 |
"register-resident double FMA chains (the DFMA "
|
| 336 |
"pipe)", clk, "high")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
mma = {}
|
| 338 |
mma_klanes = {}
|
| 339 |
-
|
|
|
|
| 340 |
it = 30_000 if quick else 100_000
|
| 341 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 342 |
if r != 0:
|
| 343 |
mma[name] = "unsupported"
|
| 344 |
continue
|
| 345 |
_sync()
|
| 346 |
-
|
| 347 |
-
# the executed K-lane count (exact at these magnitudes)
|
| 348 |
-
cal_iters = 512
|
| 349 |
-
sink[1] = 0.0
|
| 350 |
-
ops.mk_mma(kind, 1, 1, cal_iters, sink)
|
| 351 |
-
_sync()
|
| 352 |
-
gain_per_iter = float(sink[1].item()) / cal_iters
|
| 353 |
mma_klanes[name] = {
|
| 354 |
-
"executed_k_per_iter": round(
|
| 355 |
-
"vs_nominal": round(
|
|
|
|
| 356 |
}
|
|
|
|
|
|
|
| 357 |
ms = _median(_events(
|
| 358 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 362 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 363 |
-
"2:4-sparse fp16 via inline PTX m16n8k32;
|
| 364 |
-
"
|
| 365 |
-
"flops derive from the mean accumulator read-back "
|
| 366 |
"with every fragment element observed, never the "
|
| 367 |
-
"declared loop shape"
|
|
|
|
| 368 |
clk, "medium",
|
| 369 |
"dense-tile rate without memory traffic; real "
|
| 370 |
"kernels see this only when staging hides behind it")
|
| 371 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
# ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
it_sm = 30_000 if quick else 100_000
|
| 375 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 376 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
@@ -380,8 +483,23 @@ def probe_device(device="cuda", quick=False):
|
|
| 380 |
"block-resident read+write streaming (8 B per "
|
| 381 |
"thread-iteration), aggregated across SMs", clk,
|
| 382 |
"medium",
|
| 383 |
-
"bank-conflict-light access pattern;
|
| 384 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
# ---- PCIe / host transfer bandwidth
|
| 387 |
nbytes = 256 << 20
|
|
@@ -412,9 +530,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 412 |
"d2d counts one direction of traffic")
|
| 413 |
del host_pin, host_pag, dev_t, dev_u
|
| 414 |
|
| 415 |
-
# ---- atomic contention curve
|
| 416 |
-
# addresses the whole grid hammers (slots = 1 is full contention;
|
| 417 |
-
# slots = threads is contention-free), plus the shared-memory point
|
| 418 |
n_thr = blocks * 256
|
| 419 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 420 |
at = {"curve_slots": {}}
|
|
@@ -424,7 +540,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 424 |
buf.zero_()
|
| 425 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 426 |
ms = _median(_events(
|
| 427 |
-
lambda
|
| 428 |
reps))
|
| 429 |
at["curve_slots"][str(slots)] = round(
|
| 430 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
@@ -437,7 +553,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 437 |
"of distinct global addresses from 1 (full "
|
| 438 |
"contention) to one per thread", clk, "high")
|
| 439 |
|
| 440 |
-
# ---- launch overhead
|
| 441 |
anchor = sink
|
| 442 |
n_launch = 500 if quick else 2000
|
| 443 |
_sync()
|
|
@@ -452,10 +568,19 @@ def probe_device(device="cuda", quick=False):
|
|
| 452 |
ops.mk_empty(anchor)
|
| 453 |
_sync()
|
| 454 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
|
|
|
|
|
|
| 455 |
d["launch_overhead"] = _entry(
|
| 456 |
-
{"
|
| 457 |
-
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
# ---- grid barrier
|
| 461 |
bar_iters = 500 if quick else 2000
|
|
@@ -471,9 +596,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 471 |
"unavailable or grid too large", clk,
|
| 472 |
"high")
|
| 473 |
|
| 474 |
-
# ---- occupancy
|
| 475 |
-
# at every (threads, dyn_smem) point; when the forced blocks/SM stop
|
| 476 |
-
# covering memory latency, achieved bandwidth falls off the cliff
|
| 477 |
occ = {}
|
| 478 |
for threads in (128, 256, 512, 1024):
|
| 479 |
row = {}
|
|
@@ -494,8 +617,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 494 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 495 |
knee_iters, sink)
|
| 496 |
ms = _median(_events(
|
| 497 |
-
lambda t=threads,
|
| 498 |
-
knee_blocks, t,
|
| 499 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 500 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 501 |
knees[f"t{threads}"] = row
|
|
@@ -504,27 +627,160 @@ def probe_device(device="cuda", quick=False):
|
|
| 504 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 505 |
"the cliff marks where occupancy stops covering latency",
|
| 506 |
clk, "high", "timing-derived counterpart to the API table above")
|
|
|
|
|
|
|
|
|
|
| 507 |
return d
|
| 508 |
|
| 509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 511 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 512 |
-
device="cuda"):
|
| 513 |
-
"""Median-of-N cudaEvent timing with throttle rejection.
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
around every sample, ~0.3 ms each."""
|
| 523 |
for _ in range(warmup):
|
| 524 |
fn()
|
| 525 |
_sync()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
graph_state = "off"
|
| 527 |
graph_reason = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
if graph:
|
| 529 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 530 |
try:
|
|
@@ -539,10 +795,7 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 539 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 540 |
graph_state = "fallback_eager"
|
| 541 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 542 |
-
# drain the invalidated stream
|
| 543 |
-
# On some torch versions a failed capture also wedges the
|
| 544 |
-
# device RNG with no in-process recovery; timing uses no RNG,
|
| 545 |
-
# but the caller's later randn may raise.
|
| 546 |
rng_ok = True
|
| 547 |
for _attempt in range(5):
|
| 548 |
try:
|
|
@@ -572,25 +825,51 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 572 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 573 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 574 |
best_clk = max(clocks)
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
flat = []
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
kept.append(ms)
|
| 585 |
else:
|
| 586 |
rejected += 1
|
| 587 |
-
|
|
|
|
| 588 |
kept = flat
|
| 589 |
med = _median(kept)
|
| 590 |
ks = sorted(kept)
|
| 591 |
n_k = len(ks)
|
| 592 |
-
# order-statistic 95% CI on the median
|
| 593 |
-
# binomial ranks); degenerate at tiny n, where it spans the sample
|
| 594 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 595 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 596 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
@@ -605,12 +884,25 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 605 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 606 |
"samples": len(flat),
|
| 607 |
"rejected_throttled": rejected,
|
|
|
|
| 608 |
"graph_captured": graph_state == "captured",
|
| 609 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 610 |
"worst": round(min(clocks), 3)},
|
| 611 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
if graph_reason:
|
| 613 |
rep["graph_fallback_reason"] = graph_reason
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
if bytes is not None:
|
| 615 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 616 |
if flops is not None:
|
|
@@ -620,6 +912,8 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 620 |
if dossier is not None and bytes and flops:
|
| 621 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 622 |
mma_v = dossier["mma"]["value"].get(dtype)
|
|
|
|
|
|
|
| 623 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 624 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 625 |
ai = flops / bytes
|
|
@@ -630,13 +924,12 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 630 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 631 |
"peak_dtype": dtype,
|
| 632 |
}
|
|
|
|
| 633 |
return rep
|
| 634 |
|
| 635 |
|
| 636 |
def _to_ordered_int(t):
|
| 637 |
-
"""
|
| 638 |
-
distance: positive floats keep their bits, negative floats mirror
|
| 639 |
-
below zero, so the mapping is monotone in the float value."""
|
| 640 |
if t.dtype == torch.float32:
|
| 641 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 642 |
half = 1 << 31
|
|
@@ -652,11 +945,12 @@ def _to_ordered_int(t):
|
|
| 652 |
|
| 653 |
|
| 654 |
def ulp_diff(a, b):
|
| 655 |
-
"""Elementwise ULP distance
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
|
|
|
| 660 |
if not a.is_floating_point():
|
| 661 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 662 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
@@ -673,9 +967,7 @@ class order_shuffles:
|
|
| 673 |
|
| 674 |
@staticmethod
|
| 675 |
def last_dim():
|
| 676 |
-
"""
|
| 677 |
-
accumulation order of a last-dim reduction changes, the value
|
| 678 |
-
does not."""
|
| 679 |
def shuffle(args, seed):
|
| 680 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 681 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
@@ -685,8 +977,7 @@ class order_shuffles:
|
|
| 685 |
|
| 686 |
@staticmethod
|
| 687 |
def matmul_k():
|
| 688 |
-
"""
|
| 689 |
-
product is unchanged, the accumulation order is not."""
|
| 690 |
def shuffle(args, seed):
|
| 691 |
a_m, b_m = args[0], args[1]
|
| 692 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
@@ -700,37 +991,70 @@ def _as_outputs(x):
|
|
| 700 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 701 |
|
| 702 |
|
| 703 |
-
def
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
worst = {"max_ulp": -1}
|
| 722 |
total_hist = {}
|
| 723 |
per_trial = []
|
| 724 |
max_abs_seen = 0.0
|
| 725 |
max_rel_seen = 0.0
|
|
|
|
| 726 |
for t in range(trials):
|
| 727 |
args = gen(seed + t)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
r_outs = _as_outputs(ref(*args))
|
| 729 |
c_outs = _as_outputs(cand(*args))
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
bands = None
|
| 735 |
band = 0
|
| 736 |
band_score = 0.0
|
|
@@ -754,8 +1078,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 754 |
max_ulp = 0
|
| 755 |
mean_ulp = 0.0
|
| 756 |
first_div = -1
|
|
|
|
| 757 |
n_el = 0
|
| 758 |
n_above_band = 0
|
|
|
|
|
|
|
| 759 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 760 |
worst_elements = []
|
| 761 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
@@ -773,8 +1100,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 773 |
mean_ulp += float(d.double().sum().item())
|
| 774 |
n_el += d.numel()
|
| 775 |
nz = (d > 0)
|
|
|
|
|
|
|
| 776 |
if first_div < 0 and nz.any():
|
| 777 |
first_div = int(nz.flatten().nonzero()[0].item())
|
|
|
|
| 778 |
if nz.any():
|
| 779 |
buckets = torch.clamp(
|
| 780 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
@@ -791,6 +1121,7 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 791 |
if val > 0:
|
| 792 |
worst_elements.append({
|
| 793 |
"output": oi, "index": int(ix),
|
|
|
|
| 794 |
"ref": float(rf[ix].item()),
|
| 795 |
"cand": float(cf[ix].item()),
|
| 796 |
"ulp": int(val)})
|
|
@@ -810,23 +1141,27 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 810 |
inf_r += int(torch.isinf(r).sum().item())
|
| 811 |
inf_c += int(torch.isinf(c).sum().item())
|
| 812 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
|
|
|
| 813 |
trial_rep = {
|
| 814 |
"max_ulp": max_ulp,
|
| 815 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 816 |
"first_divergent_index": first_div,
|
|
|
|
| 817 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 818 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 819 |
"order_band_ulp": band,
|
| 820 |
"band_score": round(band_score, 2),
|
| 821 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
|
|
|
|
|
|
| 822 |
"worst_elements": worst_elements[:worst_k],
|
| 823 |
}
|
| 824 |
per_trial.append(trial_rep)
|
| 825 |
if max_ulp > worst["max_ulp"]:
|
| 826 |
worst = trial_rep
|
| 827 |
-
band_max = max(t["order_band_ulp"] for t in per_trial)
|
| 828 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 829 |
-
|
|
|
|
| 830 |
score = max(t["band_score"] for t in per_trial)
|
| 831 |
ulp_ok = score <= band_margin
|
| 832 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
@@ -841,8 +1176,34 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 841 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 842 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 843 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 844 |
-
|
| 845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
"criterion": criterion,
|
| 847 |
"worst_trial": worst,
|
| 848 |
"max_abs": max_abs_seen,
|
|
@@ -850,6 +1211,9 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 850 |
"trials": per_trial,
|
| 851 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 852 |
}
|
|
|
|
|
|
|
|
|
|
| 853 |
|
| 854 |
|
| 855 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
@@ -865,6 +1229,30 @@ def _context_alive(device):
|
|
| 865 |
return False
|
| 866 |
|
| 867 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
def _fuzz_build(make, shape, dtype):
|
| 869 |
try:
|
| 870 |
out = make(tuple(shape), dtype)
|
|
@@ -881,7 +1269,7 @@ def _fuzz_variant(x, v):
|
|
| 881 |
if v == "strided_rows":
|
| 882 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 883 |
if v == "offset_slice":
|
| 884 |
-
#
|
| 885 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 886 |
if v == "overflow_expand":
|
| 887 |
if x.numel() == 0:
|
|
@@ -895,8 +1283,7 @@ def _fuzz_variant(x, v):
|
|
| 895 |
|
| 896 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 897 |
stop_on_context_loss=True):
|
| 898 |
-
"""All variant cases for one (shape, dtype) group; returns (rows,
|
| 899 |
-
context_lost)."""
|
| 900 |
rows = []
|
| 901 |
bases = _fuzz_build(make, shape, dtype)
|
| 902 |
lost = False
|
|
@@ -933,8 +1320,7 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 933 |
got = op(*xs)
|
| 934 |
want = ref(*xs)
|
| 935 |
if v == "overflow_expand":
|
| 936 |
-
# bounded comparison:
|
| 937 |
-
# which is where int32 flat indexing wraps
|
| 938 |
w = 1 << 20
|
| 939 |
gf, wf = got.flatten(), want.flatten()
|
| 940 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
@@ -959,20 +1345,30 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 959 |
return rows, lost
|
| 960 |
|
| 961 |
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
import
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 974 |
meta = json.load(open(sys.argv[1], "r"))
|
| 975 |
-
sys.path[:0] = meta["sys_path"]
|
| 976 |
try:
|
| 977 |
import kernels
|
| 978 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
@@ -985,85 +1381,165 @@ except Exception:
|
|
| 985 |
import load_local
|
| 986 |
_mk = load_local.load()
|
| 987 |
import torch
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 994 |
"""
|
| 995 |
|
| 996 |
|
| 997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 999 |
-
isolate_timeout_s=180, device="cuda"):
|
| 1000 |
-
"""
|
| 1001 |
-
report ok / mismatch / exception per case.
|
| 1002 |
-
|
| 1003 |
-
`make(shape, dtype)` (or `make(shape)`) builds a tensor or tuple;
|
| 1004 |
-
layout variants derive non-contiguous and misaligned views whose
|
| 1005 |
-
content equals a contiguous copy. `overflow_expand` stride-0-expands
|
| 1006 |
-
past 2**31 logical elements to catch int32 indexing (`skipped_vram`
|
| 1007 |
-
when it would not fit). Every case carries a `repro` recipe.
|
| 1008 |
-
In-process, the CUDA context is health-checked after exceptions and
|
| 1009 |
-
remaining cases marked `context_lost` if killed; `isolate=True`
|
| 1010 |
-
spawns each (shape, dtype) group in a subprocess, so a
|
| 1011 |
-
context-killing candidate costs one group (`crashed_isolated`);
|
| 1012 |
-
op/ref/make must be picklable."""
|
| 1013 |
if shapes is None:
|
| 1014 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1015 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
|
|
|
|
|
|
| 1016 |
|
| 1017 |
rows = []
|
| 1018 |
lost = False
|
| 1019 |
if isolate:
|
| 1020 |
import json
|
| 1021 |
import pickle
|
| 1022 |
-
import subprocess
|
| 1023 |
import sys as _sys
|
| 1024 |
import tempfile
|
| 1025 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1026 |
fns = os.path.join(tmpdir, "fns.pkl")
|
|
|
|
|
|
|
| 1027 |
with open(fns, "wb") as f:
|
| 1028 |
-
pickle.dump(
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
"shape": list(shape),
|
| 1041 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1042 |
"variants": list(variants),
|
| 1043 |
"ulp_tol": ulp_tol, "device": device,
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
try:
|
| 1048 |
-
r = subprocess.run(
|
| 1049 |
-
[_sys.executable, "-c", _ISOLATE_BOOTSTRAP, meta,
|
| 1050 |
-
fns, out], timeout=isolate_timeout_s,
|
| 1051 |
-
capture_output=True)
|
| 1052 |
-
if r.returncode == 0 and os.path.exists(out):
|
| 1053 |
with open(out, "rb") as f:
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
"status": "crashed_isolated",
|
| 1064 |
-
"exitcode": exitcode,
|
| 1065 |
-
"stderr_tail": stderr_tail}]
|
| 1066 |
-
rows.extend(group)
|
| 1067 |
else:
|
| 1068 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1069 |
for gi, (dtype, shape) in enumerate(pending):
|
|
@@ -1080,21 +1556,27 @@ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
|
|
| 1080 |
n_bad = sum(1 for r in rows
|
| 1081 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1082 |
"skipped_empty"))
|
| 1083 |
-
|
| 1084 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
|
| 1086 |
|
| 1087 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1088 |
check=None, budget_s=None, race=False):
|
| 1089 |
-
"""Launch-configuration search
|
| 1090 |
-
bench row per grid point, sorted fastest first. A point whose factory
|
| 1091 |
-
or run raises is reported, not skipped silently. `check(fn) -> bool`
|
| 1092 |
-
is an optional correctness gate recorded per row (a fast wrong config
|
| 1093 |
-
must not win a sweep). `budget_s` caps total sweep time; points not
|
| 1094 |
-
reached are reported as `not_run` rather than silently dropped.
|
| 1095 |
-
`race=True` runs successive halving: a quick pass over every point at
|
| 1096 |
-
iters/4, then the full budget only on the fastest quartile; pruned
|
| 1097 |
-
points keep their quick measurement and status `pruned_race`."""
|
| 1098 |
keys = list(grid.keys())
|
| 1099 |
|
| 1100 |
def expand(i, acc):
|
|
@@ -1159,13 +1641,71 @@ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
|
| 1159 |
continue
|
| 1160 |
rows.append(run_point(p, iters))
|
| 1161 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
return rows
|
| 1163 |
|
| 1164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1165 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1166 |
-
"""Device buffer for
|
| 1167 |
-
stamps.cuh): the candidate kernel writes clock64() into slot i. With
|
| 1168 |
-
`blocks`, allocates the [blocks, n] layout for MK_STAMP_BLOCK."""
|
| 1169 |
if blocks is not None:
|
| 1170 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1171 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
@@ -1175,8 +1715,7 @@ _DRIFT_SKIP = ("device", "clock", "launch_overhead", "transfer_bw")
|
|
| 1175 |
|
| 1176 |
|
| 1177 |
def pinned_dossiers():
|
| 1178 |
-
"""Names of the
|
| 1179 |
-
architecture)."""
|
| 1180 |
return sorted(_PINNED.keys())
|
| 1181 |
|
| 1182 |
|
|
@@ -1195,11 +1734,7 @@ def _numeric_leaves(d, prefix=""):
|
|
| 1195 |
|
| 1196 |
|
| 1197 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1198 |
-
"""
|
| 1199 |
-
same architecture: per-leaf relative drift, worst drift, entries
|
| 1200 |
-
over `rel_tol`. Host-dependent entries and raw clocks are skipped by
|
| 1201 |
-
default (`skip=()` compares everything); `pinned=None` auto-selects
|
| 1202 |
-
by sm_arch and raises if no pin exists."""
|
| 1203 |
if pinned is None:
|
| 1204 |
arch = current["device"]["sm_arch"]
|
| 1205 |
matches = [k for k, v in _PINNED.items()
|
|
@@ -1222,16 +1757,19 @@ def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
|
| 1222 |
"rel": round(rel, 4)})
|
| 1223 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1224 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
|
| 1229 |
|
| 1230 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1231 |
device="cuda"):
|
| 1232 |
-
"""Run the shipped MK_STAMP_BLOCK example
|
| 1233 |
-
compute, global store) and decode its stamps; a template for
|
| 1234 |
-
instrumenting a candidate."""
|
| 1235 |
props = torch.cuda.get_device_properties(device)
|
| 1236 |
if blocks is None:
|
| 1237 |
blocks = props.multi_processor_count
|
|
@@ -1244,11 +1782,7 @@ def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
|
| 1244 |
|
| 1245 |
|
| 1246 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1247 |
-
"""Decode MK_STAMP
|
| 1248 |
-
microseconds under the measured (or provided) SM clock. A 1-D buffer
|
| 1249 |
-
(MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
|
| 1250 |
-
buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
|
| 1251 |
-
phase, with blocks whose stamps stayed zero excluded."""
|
| 1252 |
if clock_ghz is None:
|
| 1253 |
clock_ghz = measure_clock(stamps.device)
|
| 1254 |
|
|
@@ -1284,3 +1818,104 @@ def read_stamps(stamps, labels=None, clock_ghz=None):
|
|
| 1284 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1285 |
"blocks_reporting": int(live.sum().item()),
|
| 1286 |
"blocks_total": int(live.numel())}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Measuring instrument for the kernel-design loop: measured device facts, throttle-aware timing, adversarial correctness comparison, all as loadable ops returning plain data."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import math
|
| 3 |
import os
|
| 4 |
import time
|
|
|
|
| 8 |
try:
|
| 9 |
from ._ops import ops
|
| 10 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 11 |
+
# children re-import the package; a source checkout rebuilds the op table from the cached JIT extension
|
|
|
|
|
|
|
| 12 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 13 |
if not _root:
|
| 14 |
raise
|
|
|
|
| 30 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 31 |
_PINNED = {}
|
| 32 |
|
| 33 |
+
__all__ = ["probe_device", "probe_all_devices", "bench", "compare", "judge",
|
| 34 |
+
"order_shuffles", "fuzz_shapes", "sweep", "alloc_stamps",
|
| 35 |
+
"read_stamps", "stamped_demo", "measure_clock", "ulp_diff",
|
| 36 |
+
"dossier_drift", "pinned_dossiers", "mma_tile_check", "schema",
|
| 37 |
+
"probe_capturable", "ops"]
|
| 38 |
+
|
| 39 |
+
_REPO_ID = "phanerozoic/metakernel"
|
| 40 |
+
_REPO_VERSION = 1
|
| 41 |
+
_PROBE_SCHEMA = 2
|
| 42 |
+
|
| 43 |
+
# kind id, C-tile M*N, nominal K, nominal K-lanes per unrolled loop iteration
|
| 44 |
_MMA_KINDS = {
|
| 45 |
+
"fp16": (0, 16 * 16, 16, 64.0),
|
| 46 |
+
"bf16": (1, 16 * 16, 16, 64.0),
|
| 47 |
+
"tf32": (2, 16 * 16, 8, 32.0),
|
| 48 |
+
"int8": (3, 16 * 16, 16, 64.0),
|
| 49 |
+
"fp8_e4m3": (4, 16 * 8, 32, 256.0),
|
| 50 |
+
"fp16_sparse_2_4": (5, 16 * 8, 32, 128.0),
|
| 51 |
+
"fp8_e5m2": (6, 16 * 8, 32, 256.0),
|
| 52 |
}
|
| 53 |
|
| 54 |
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def _events(fn, reps):
|
| 60 |
+
"""Time reps calls of fn with one cudaEvent pair each; returns a ms list."""
|
| 61 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 62 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 63 |
for s, e in zip(starts, stops):
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 72 |
+
"""Effective SM clock in GHz from clock64 ticks over wall time under an FMA spin."""
|
|
|
|
| 73 |
props = torch.cuda.get_device_properties(device)
|
| 74 |
if blocks is None:
|
| 75 |
blocks = props.multi_processor_count
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
def _sattolo_ring(n, device, generator=None):
|
| 105 |
+
"""Random single-cycle permutation as an int32 next-pointer table."""
|
|
|
|
| 106 |
perm = torch.randperm(n, generator=generator)
|
| 107 |
ring = torch.empty(n, dtype=torch.int32)
|
| 108 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
|
|
| 110 |
|
| 111 |
|
| 112 |
def _smi_settings(index):
|
| 113 |
+
"""ECC mode, driver, power limit via nvidia-smi; None fields when unavailable."""
|
|
|
|
| 114 |
import subprocess
|
| 115 |
try:
|
| 116 |
out = subprocess.run(
|
|
|
|
| 130 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 131 |
|
| 132 |
|
| 133 |
+
def _unravel(flat, shape):
|
| 134 |
+
"""Multi-dimensional index for a flat index into shape."""
|
| 135 |
+
if not shape:
|
| 136 |
+
return []
|
| 137 |
+
out = []
|
| 138 |
+
for s in reversed(list(shape)):
|
| 139 |
+
out.append(int(flat % s))
|
| 140 |
+
flat //= s
|
| 141 |
+
return list(reversed(out))
|
| 142 |
+
|
| 143 |
+
|
| 144 |
def mma_tile_check(device="cuda", trials=8):
|
| 145 |
+
"""Random small-integer m16n16k16 tiles through the wmma path, compared exactly to the reference GEMM."""
|
|
|
|
|
|
|
| 146 |
worst = 0.0
|
| 147 |
for t in range(trials):
|
| 148 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
|
|
| 160 |
"trials": trials}
|
| 161 |
|
| 162 |
|
| 163 |
+
def _tenancy_canary(device, rel_tol=0.10, attempts=3):
|
| 164 |
+
"""Fixed-cost triad timed in paired rounds; busy only when every attempt disagrees."""
|
| 165 |
+
n4 = 4 << 20
|
| 166 |
+
a = torch.rand(n4 * 4, device=device)
|
| 167 |
+
b = torch.rand(n4 * 4, device=device)
|
| 168 |
+
c = torch.empty(n4 * 4, device=device)
|
| 169 |
+
ops.mk_triad(a, b, c, 4, 1.5)
|
| 170 |
+
rels = []
|
| 171 |
+
for _ in range(attempts):
|
| 172 |
+
r1 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 173 |
+
r2 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 174 |
+
rel = abs(r1 - r2) / min(r1, r2)
|
| 175 |
+
rels.append(round(rel, 4))
|
| 176 |
+
if rel <= rel_tol:
|
| 177 |
+
return False, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 178 |
+
return True, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _cache_dir():
|
| 182 |
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~/.cache")
|
| 183 |
+
d = os.path.join(base, "metakernel")
|
| 184 |
+
os.makedirs(d, exist_ok=True)
|
| 185 |
+
return d
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _cache_path(device, quick):
|
| 189 |
+
import re
|
| 190 |
+
props = torch.cuda.get_device_properties(device)
|
| 191 |
+
smi = _smi_settings(torch.device(device).index or 0)
|
| 192 |
+
slug = re.sub(r"[^a-z0-9]+", "", props.name.lower())[:24]
|
| 193 |
+
key = "sm%d%d_%s_drv%s_cu%s_q%d_s%d" % (
|
| 194 |
+
props.major, props.minor, slug, smi["driver"] or "na",
|
| 195 |
+
torch.version.cuda, int(quick), _PROBE_SCHEMA)
|
| 196 |
+
return os.path.join(_cache_dir(), key + ".json")
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _mma_calibrate(kind_id, sink):
|
| 200 |
+
"""Executed K-lanes per loop iteration from a sub-saturation accumulator read-back, with a linearity check."""
|
| 201 |
+
gains = []
|
| 202 |
+
for cal in (4, 32):
|
| 203 |
+
sink[1] = 0.0
|
| 204 |
+
ops.mk_mma(kind_id, 1, 1, cal, sink)
|
| 205 |
+
_sync()
|
| 206 |
+
gains.append(float(sink[1].item()) / cal)
|
| 207 |
+
linear = gains[0] > 0 and abs(gains[1] - gains[0]) / gains[0] < 0.02
|
| 208 |
+
return gains[0], linear
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _fp8_accum_stall(kind_id, sink, k_exec):
|
| 212 |
+
"""Growth curve of the fp8 accumulator; a stall reveals the effective accumulate mantissa width."""
|
| 213 |
+
vals = []
|
| 214 |
+
for it in (32, 64, 128, 256, 512):
|
| 215 |
+
sink[1] = 0.0
|
| 216 |
+
ops.mk_mma(kind_id, 1, 1, it, sink)
|
| 217 |
+
_sync()
|
| 218 |
+
vals.append(float(sink[1].item()) / 2.0)
|
| 219 |
+
if vals[-1] == vals[-2] and vals[-1] < vals[0] * 16 * 0.99:
|
| 220 |
+
bits = int(round(math.log2(vals[-1] / (2.0 * k_exec))))
|
| 221 |
+
return {"stall_element_value": vals[-1],
|
| 222 |
+
"effective_accum_mantissa_bits": bits}
|
| 223 |
+
return {"stall_element_value": None,
|
| 224 |
+
"effective_accum_mantissa_bits": None}
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def probe_device(device="cuda", quick=False, cached=True,
|
| 228 |
+
tenancy_check=True):
|
| 229 |
+
"""Empirical device dossier; every entry measured by a dedicated probe kernel and stamped with its SM clock."""
|
| 230 |
+
cpath = _cache_path(device, quick)
|
| 231 |
+
if cached and os.path.exists(cpath):
|
| 232 |
+
import json
|
| 233 |
+
return json.load(open(cpath, encoding="utf-8"))
|
| 234 |
+
if tenancy_check:
|
| 235 |
+
busy, ev = _tenancy_canary(device)
|
| 236 |
+
if busy:
|
| 237 |
+
raise RuntimeError("device_busy: canary rounds disagree %s; "
|
| 238 |
+
"idle the device or pass "
|
| 239 |
+
"tenancy_check=False" % ev)
|
| 240 |
props = torch.cuda.get_device_properties(device)
|
| 241 |
sm = props.multi_processor_count
|
| 242 |
l2 = props.L2_cache_size
|
|
|
|
| 249 |
"vram_bytes": props.total_memory,
|
| 250 |
"torch": torch.__version__,
|
| 251 |
"cuda_runtime": torch.version.cuda,
|
| 252 |
+
# instrument settings recorded because readings depend on them
|
|
|
|
| 253 |
**_smi_settings(torch.device(device).index or 0),
|
| 254 |
}
|
| 255 |
}
|
|
|
|
| 274 |
}
|
| 275 |
clk = max(traj)
|
| 276 |
|
| 277 |
+
# ---- device-memory bandwidth per access width plus read/write/gather
|
| 278 |
+
n4 = 16 << 20
|
|
|
|
| 279 |
if props.total_memory < 4 << 30:
|
| 280 |
n4 = 4 << 20
|
| 281 |
a = torch.rand(n4 * 4, device=device)
|
|
|
|
| 299 |
dtype=torch.int32).to(device)
|
| 300 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 301 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
|
|
|
| 302 |
hbm["random_gather_16B"] = round(
|
| 303 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 304 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
|
|
| 309 |
"so its nominal number is the achievable rate for "
|
| 310 |
"random access, not a line-efficiency claim")
|
| 311 |
|
| 312 |
+
# ---- L2 bandwidth over an L2-resident working set
|
| 313 |
ws = max(l2 // 4, 1 << 20)
|
| 314 |
+
nl2 = ws // 16
|
| 315 |
al = a[:nl2 * 4].contiguous()
|
| 316 |
bl = b[:nl2 * 4].contiguous()
|
| 317 |
cl = c[:nl2 * 4].contiguous()
|
|
|
|
| 326 |
"medium", "first pass streams from HBM; amortized "
|
| 327 |
f"over {passes} passes")
|
| 328 |
|
| 329 |
+
# ---- latencies: min of 3 chases per level so a co-tenant burst cannot land in the dossier
|
|
|
|
|
|
|
| 330 |
lat = {}
|
| 331 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
+
def chase(op, ring, warm_hops, hops, extra=None):
|
| 334 |
+
best = None
|
| 335 |
+
for _ in range(3):
|
| 336 |
+
if extra is None:
|
| 337 |
+
op(ring, warm_hops, out64)
|
| 338 |
+
_sync()
|
| 339 |
+
op(ring, hops, out64)
|
| 340 |
+
else:
|
| 341 |
+
op(ring, extra, warm_hops, out64)
|
| 342 |
+
_sync()
|
| 343 |
+
op(ring, extra, hops, out64)
|
| 344 |
+
_sync()
|
| 345 |
+
t = int(out64[0].item()) / hops
|
| 346 |
+
best = t if best is None else min(best, t)
|
| 347 |
+
return best
|
|
|
|
|
|
|
| 348 |
|
| 349 |
+
ring_s = _sattolo_ring(8192, device)
|
| 350 |
+
lat["smem_cycles"] = round(
|
| 351 |
+
chase(lambda r, h, o: ops.mk_chase_shared(r, h, o), ring_s,
|
| 352 |
+
1 << 14, 1 << 16), 1)
|
| 353 |
+
ring_l1 = _sattolo_ring((16 << 10) // 4, device)
|
| 354 |
+
lat["l1_cycles"] = round(
|
| 355 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l1,
|
| 356 |
+
ring_l1.numel(), 1 << 15), 1)
|
| 357 |
+
ring_l2 = _sattolo_ring((2 << 20) // 4, device)
|
| 358 |
+
lat["l2_cycles"] = round(
|
| 359 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l2,
|
| 360 |
+
ring_l2.numel(), 1 << 14), 1)
|
| 361 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 362 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 363 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 364 |
+
lat["dram_cycles"] = round(
|
| 365 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_h,
|
| 366 |
+
1 << 14, hops_h), 1)
|
|
|
|
|
|
|
| 367 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 368 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 369 |
+
"Sattolo-cycle pointer chase, min of 3 runs; 16 "
|
| 370 |
+
"KB ring for L1, 2 MB TLB-resident ring for L2, "
|
| 371 |
+
"self-evicting chain (2x L2 in touched lines) "
|
| 372 |
+
"for HBM", idle_clk, "high",
|
| 373 |
"smem includes index arithmetic; HBM includes "
|
| 374 |
"residual TLB effects at large footprints")
|
| 375 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 376 |
|
| 377 |
+
# ---- fp32/fp64 FMA and tensor-core throughput
|
| 378 |
blocks = sm * 4
|
| 379 |
iters = 20_000 if quick else 60_000
|
| 380 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 381 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 382 |
reps))
|
| 383 |
+
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 384 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 385 |
"register-resident FMA chains", clk, "high")
|
| 386 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
|
|
| 392 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 393 |
"register-resident double FMA chains (the DFMA "
|
| 394 |
"pipe)", clk, "high")
|
| 395 |
+
|
| 396 |
+
# ---- ILP ladder: 1/2/4 chains at low occupancy separates latency-bound from pipe-bound
|
| 397 |
+
ilp = {}
|
| 398 |
+
it_ilp = 20_000 if quick else 60_000
|
| 399 |
+
for ch in (1, 2, 4):
|
| 400 |
+
ops.mk_fma_ilp(ch, sm, 64, it_ilp, sink)
|
| 401 |
+
ms = _median(_events(
|
| 402 |
+
lambda c2=ch: ops.mk_fma_ilp(c2, sm, 64, it_ilp, sink), reps))
|
| 403 |
+
ilp[f"ilp{ch}"] = round(
|
| 404 |
+
sm * 64 * it_ilp * 16 * 2 * ch / (ms * 1e-3) / 1e12, 2)
|
| 405 |
+
ilp["ratio_4_1"] = round(ilp["ilp4"] / ilp["ilp1"], 2)
|
| 406 |
+
d["fma_ilp"] = _entry(ilp, "TFLOP/s",
|
| 407 |
+
"1/2/4 independent dependent FMA chains per "
|
| 408 |
+
"thread at one block of 64 threads per SM; a "
|
| 409 |
+
"ratio near 4 means latency-bound at that "
|
| 410 |
+
"occupancy", clk, "high")
|
| 411 |
+
|
| 412 |
mma = {}
|
| 413 |
mma_klanes = {}
|
| 414 |
+
fp8_accum = {}
|
| 415 |
+
for name, (kind, mn, k_nom, nom_lanes) in _MMA_KINDS.items():
|
| 416 |
it = 30_000 if quick else 100_000
|
| 417 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 418 |
if r != 0:
|
| 419 |
mma[name] = "unsupported"
|
| 420 |
continue
|
| 421 |
_sync()
|
| 422 |
+
gain, linear = _mma_calibrate(kind, sink)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
mma_klanes[name] = {
|
| 424 |
+
"executed_k_per_iter": round(gain, 2),
|
| 425 |
+
"vs_nominal": round(gain / nom_lanes, 3),
|
| 426 |
+
"calibration_linear": linear,
|
| 427 |
}
|
| 428 |
+
if name.startswith("fp8"):
|
| 429 |
+
fp8_accum[name] = _fp8_accum_stall(kind, sink, k_nom)
|
| 430 |
ms = _median(_events(
|
| 431 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 432 |
+
tflops = blocks * 8 * it * 2.0 * mn * gain / (ms * 1e-3) / 1e12
|
| 433 |
+
if name == "fp16_sparse_2_4":
|
| 434 |
+
mma[name] = {"executed": round(tflops, 1),
|
| 435 |
+
"dense_equivalent": round(2 * tflops, 1)}
|
| 436 |
+
else:
|
| 437 |
+
mma[name] = round(tflops, 1)
|
| 438 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 439 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 440 |
+
"2:4-sparse fp16 via inline PTX m16n8k32); flops "
|
| 441 |
+
"derive from a sub-saturation accumulator read-back "
|
|
|
|
| 442 |
"with every fragment element observed, never the "
|
| 443 |
+
"declared loop shape; sparse reports executed and "
|
| 444 |
+
"dense-equivalent flops",
|
| 445 |
clk, "medium",
|
| 446 |
"dense-tile rate without memory traffic; real "
|
| 447 |
"kernels see this only when staging hides behind it")
|
| 448 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
| 449 |
+
if fp8_accum:
|
| 450 |
+
d["mma"]["fp8_accum"] = fp8_accum
|
| 451 |
|
| 452 |
+
# ---- fed MMA: smem-staged fp16 tiles under cp.async double buffering
|
| 453 |
+
if props.major >= 8:
|
| 454 |
+
gbuf = torch.ones(16 << 20, dtype=torch.float16, device=device)
|
| 455 |
+
sink[1] = 0.0
|
| 456 |
+
r = ops.mk_mma_fed(gbuf, 1, 1, 64, sink)
|
| 457 |
+
if r == 0:
|
| 458 |
+
_sync()
|
| 459 |
+
fed_gain = float(sink[1].item()) / 64
|
| 460 |
+
it_fed = 6_000 if quick else 20_000
|
| 461 |
+
ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink)
|
| 462 |
+
ms = _median(_events(
|
| 463 |
+
lambda: ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink),
|
| 464 |
+
reps))
|
| 465 |
+
fed = sm * 2 * 8 * it_fed * 2.0 * 256 * fed_gain / \
|
| 466 |
+
(ms * 1e-3) / 1e12
|
| 467 |
+
d["mma_fed"] = _entry(
|
| 468 |
+
round(fed, 1), "TFLOP/s",
|
| 469 |
+
"m16n16k16 fp16 tiles fed from an L2-resident buffer "
|
| 470 |
+
"through cp.async double-buffered shared memory; "
|
| 471 |
+
"accumulator-verified", clk, "high",
|
| 472 |
+
"the achieved counterpart to the register peak: staging "
|
| 473 |
+
"exposed, one dependent accumulator chain per warp")
|
| 474 |
+
del gbuf
|
| 475 |
+
|
| 476 |
+
# ---- shared-memory bandwidth and the bank-conflict stride sweep
|
| 477 |
it_sm = 30_000 if quick else 100_000
|
| 478 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 479 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
|
|
| 483 |
"block-resident read+write streaming (8 B per "
|
| 484 |
"thread-iteration), aggregated across SMs", clk,
|
| 485 |
"medium",
|
| 486 |
+
"bank-conflict-light access pattern; the stride "
|
| 487 |
+
"sweep below prices the conflicted case")
|
| 488 |
+
strides = (1, 8, 32) if quick else (1, 2, 4, 8, 16, 32)
|
| 489 |
+
it_st = 8_000 if quick else 20_000
|
| 490 |
+
st_ms = {}
|
| 491 |
+
for s2 in strides:
|
| 492 |
+
ops.mk_smem_stride(s2, blocks, 256, it_st, sink)
|
| 493 |
+
st_ms[s2] = _median(_events(
|
| 494 |
+
lambda s3=s2: ops.mk_smem_stride(s3, blocks, 256, it_st, sink),
|
| 495 |
+
reps))
|
| 496 |
+
d["smem_conflict_penalty"] = _entry(
|
| 497 |
+
{f"stride_{s2}": round(st_ms[s2] / st_ms[1], 2) for s2 in strides},
|
| 498 |
+
"x (time ratio vs stride 1)",
|
| 499 |
+
"dependent read-FMA-write per thread at warp-lane bank distance "
|
| 500 |
+
"`stride`; stride 32 puts every lane in one bank", clk, "high",
|
| 501 |
+
"the direct conflict measurement that disambiguates a "
|
| 502 |
+
"bank-conflict signature from an ILP stall")
|
| 503 |
|
| 504 |
# ---- PCIe / host transfer bandwidth
|
| 505 |
nbytes = 256 << 20
|
|
|
|
| 530 |
"d2d counts one direction of traffic")
|
| 531 |
del host_pin, host_pag, dev_t, dev_u
|
| 532 |
|
| 533 |
+
# ---- atomic contention curve
|
|
|
|
|
|
|
| 534 |
n_thr = blocks * 256
|
| 535 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 536 |
at = {"curve_slots": {}}
|
|
|
|
| 540 |
buf.zero_()
|
| 541 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 542 |
ms = _median(_events(
|
| 543 |
+
lambda s4=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s4),
|
| 544 |
reps))
|
| 545 |
at["curve_slots"][str(slots)] = round(
|
| 546 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
|
|
| 553 |
"of distinct global addresses from 1 (full "
|
| 554 |
"contention) to one per thread", clk, "high")
|
| 555 |
|
| 556 |
+
# ---- launch overhead: torch-dispatch path and the raw C++ path
|
| 557 |
anchor = sink
|
| 558 |
n_launch = 500 if quick else 2000
|
| 559 |
_sync()
|
|
|
|
| 568 |
ops.mk_empty(anchor)
|
| 569 |
_sync()
|
| 570 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
| 571 |
+
raw_q = float(ops.mk_launch_raw(n_launch, 0, anchor))
|
| 572 |
+
raw_rt = float(ops.mk_launch_raw(n_rt, 1, anchor))
|
| 573 |
d["launch_overhead"] = _entry(
|
| 574 |
+
{"torch_queued_us": round(queued, 2),
|
| 575 |
+
"torch_round_trip_us": round(rt, 2),
|
| 576 |
+
"raw_queued_us": round(raw_q / n_launch * 1e6, 2)
|
| 577 |
+
if raw_q >= 0 else None,
|
| 578 |
+
"raw_round_trip_us": round(raw_rt / n_rt * 1e6, 2)
|
| 579 |
+
if raw_rt >= 0 else None},
|
| 580 |
+
"us", "empty-kernel trains; torch path per-launch through op "
|
| 581 |
+
"dispatch, raw path looped inside one binding call", clk,
|
| 582 |
+
"medium", "host-state-dependent; the torch-minus-raw gap is the "
|
| 583 |
+
"dispatch cost a torch-launched candidate pays")
|
| 584 |
|
| 585 |
# ---- grid barrier
|
| 586 |
bar_iters = 500 if quick else 2000
|
|
|
|
| 596 |
"unavailable or grid too large", clk,
|
| 597 |
"high")
|
| 598 |
|
| 599 |
+
# ---- occupancy API table plus timed knees
|
|
|
|
|
|
|
| 600 |
occ = {}
|
| 601 |
for threads in (128, 256, 512, 1024):
|
| 602 |
row = {}
|
|
|
|
| 617 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 618 |
knee_iters, sink)
|
| 619 |
ms = _median(_events(
|
| 620 |
+
lambda t=threads, s5=smem_kb: ops.mk_fma_dep(
|
| 621 |
+
knee_blocks, t, s5 * 1024, knee_iters, sink), reps))
|
| 622 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 623 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 624 |
knees[f"t{threads}"] = row
|
|
|
|
| 627 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 628 |
"the cliff marks where occupancy stops covering latency",
|
| 629 |
clk, "high", "timing-derived counterpart to the API table above")
|
| 630 |
+
if cached:
|
| 631 |
+
import json
|
| 632 |
+
json.dump(d, open(cpath, "w", encoding="utf-8"))
|
| 633 |
return d
|
| 634 |
|
| 635 |
|
| 636 |
+
def probe_all_devices(quick=True, cached=True, tenancy_check=True):
|
| 637 |
+
"""One dossier per visible device plus a pairwise P2P bandwidth matrix."""
|
| 638 |
+
n = torch.cuda.device_count()
|
| 639 |
+
devices = {}
|
| 640 |
+
for i in range(n):
|
| 641 |
+
devices[i] = probe_device(f"cuda:{i}", quick=quick, cached=cached,
|
| 642 |
+
tenancy_check=tenancy_check)
|
| 643 |
+
p2p = {}
|
| 644 |
+
if n > 1:
|
| 645 |
+
nbytes = 64 << 20
|
| 646 |
+
for i in range(n):
|
| 647 |
+
for j in range(n):
|
| 648 |
+
if i == j:
|
| 649 |
+
continue
|
| 650 |
+
src = torch.rand(nbytes // 4, device=f"cuda:{i}")
|
| 651 |
+
dst = torch.empty(nbytes // 4, device=f"cuda:{j}")
|
| 652 |
+
dst.copy_(src)
|
| 653 |
+
torch.cuda.synchronize(i)
|
| 654 |
+
torch.cuda.synchronize(j)
|
| 655 |
+
t0 = time.perf_counter()
|
| 656 |
+
for _ in range(5):
|
| 657 |
+
dst.copy_(src)
|
| 658 |
+
torch.cuda.synchronize(i)
|
| 659 |
+
torch.cuda.synchronize(j)
|
| 660 |
+
ms = (time.perf_counter() - t0) / 5 * 1e3
|
| 661 |
+
p2p[f"{i}->{j}"] = {
|
| 662 |
+
"gbs": round(nbytes / (ms * 1e-3) / 1e9, 1),
|
| 663 |
+
"peer_access": bool(
|
| 664 |
+
torch.cuda.can_device_access_peer(i, j)),
|
| 665 |
+
}
|
| 666 |
+
del src, dst
|
| 667 |
+
return {"devices": devices, "p2p": p2p}
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
_CAPTURE_PROBE = r"""
|
| 671 |
+
import pickle, sys, torch
|
| 672 |
+
fn = pickle.load(open(sys.argv[1], "rb"))
|
| 673 |
+
for _ in range(3):
|
| 674 |
+
fn()
|
| 675 |
+
torch.cuda.synchronize()
|
| 676 |
+
try:
|
| 677 |
+
g = torch.cuda.CUDAGraph()
|
| 678 |
+
with torch.cuda.graph(g):
|
| 679 |
+
fn()
|
| 680 |
+
print("capturable", flush=True)
|
| 681 |
+
except Exception as e:
|
| 682 |
+
print("uncapturable: %s: %s" % (type(e).__name__, str(e)[:120]),
|
| 683 |
+
flush=True)
|
| 684 |
+
"""
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def probe_capturable(fn, timeout_s=180):
|
| 688 |
+
"""Test CUDA-graph capturability of fn in a child process, so a capture-wedged RNG never reaches the caller."""
|
| 689 |
+
import pickle
|
| 690 |
+
import subprocess
|
| 691 |
+
import sys as _sys
|
| 692 |
+
import tempfile
|
| 693 |
+
try:
|
| 694 |
+
blob = pickle.dumps(fn)
|
| 695 |
+
except Exception: # noqa: BLE001
|
| 696 |
+
return {"capturable": None,
|
| 697 |
+
"reason": "fn is not picklable; probe skipped (define fn "
|
| 698 |
+
"at module top level to enable it)"}
|
| 699 |
+
path = os.path.join(tempfile.mkdtemp(prefix="mk_cap_"), "fn.pkl")
|
| 700 |
+
with open(path, "wb") as f:
|
| 701 |
+
f.write(blob)
|
| 702 |
+
try:
|
| 703 |
+
r = subprocess.run([_sys.executable, "-c", _CAPTURE_PROBE, path],
|
| 704 |
+
capture_output=True, text=True,
|
| 705 |
+
timeout=timeout_s)
|
| 706 |
+
except Exception as e: # noqa: BLE001
|
| 707 |
+
return {"capturable": None,
|
| 708 |
+
"reason": f"probe child failed: {type(e).__name__}"}
|
| 709 |
+
out = (r.stdout or "").strip()
|
| 710 |
+
if out == "capturable":
|
| 711 |
+
return {"capturable": True, "reason": ""}
|
| 712 |
+
if out.startswith("uncapturable"):
|
| 713 |
+
return {"capturable": False, "reason": out}
|
| 714 |
+
return {"capturable": None,
|
| 715 |
+
"reason": "probe child exit %s: %s" % (
|
| 716 |
+
r.returncode, (r.stderr or "")[-200:])}
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
def _bench_verdict(rep):
|
| 720 |
+
parts = ["median %.4g ms" % rep["median_ms"]]
|
| 721 |
+
if "achieved_tflops" in rep:
|
| 722 |
+
parts.append("%.4g TFLOP/s" % rep["achieved_tflops"])
|
| 723 |
+
elif "achieved_gbs" in rep:
|
| 724 |
+
parts.append("%.4g GB/s" % rep["achieved_gbs"])
|
| 725 |
+
if "roofline" in rep:
|
| 726 |
+
parts.append("%.0f%% of %s roof" % (rep["roofline"]["pct_of_roof"],
|
| 727 |
+
rep["roofline"]["bound"]))
|
| 728 |
+
if rep["rejected_throttled"]:
|
| 729 |
+
parts.append("%d/%d rejected (%s)" % (
|
| 730 |
+
rep["rejected_throttled"], rep["samples"],
|
| 731 |
+
rep["rejection_basis"]))
|
| 732 |
+
if rep.get("no_clean_window"):
|
| 733 |
+
parts.append("no clean window: the whole run was throttled")
|
| 734 |
+
if rep.get("alloc_growth_bytes_per_call"):
|
| 735 |
+
parts.append("fn allocates %d B/call" %
|
| 736 |
+
rep["alloc_growth_bytes_per_call"])
|
| 737 |
+
if rep.get("host_overhead_suspected"):
|
| 738 |
+
parts.append("fn appears to sync or do host work inside")
|
| 739 |
+
return "; ".join(parts)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 743 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 744 |
+
graph_probe_isolated=False, device="cuda"):
|
| 745 |
+
"""Median-of-N cudaEvent timing with throttle rejection, roofline placement, and footgun detection."""
|
| 746 |
+
for name, v in (("iters", iters), ("warmup", warmup)):
|
| 747 |
+
if not isinstance(v, int) or v < (1 if name == "iters" else 0):
|
| 748 |
+
raise ValueError(f"{name} must be a non-negative int; "
|
| 749 |
+
f"got {v!r}")
|
| 750 |
+
for name, v in (("bytes", bytes), ("flops", flops)):
|
| 751 |
+
if v is not None and (not isinstance(v, (int, float)) or v <= 0):
|
| 752 |
+
raise ValueError(f"{name} must be a positive number counting "
|
| 753 |
+
f"{name} per call of fn; got {v!r}")
|
|
|
|
| 754 |
for _ in range(warmup):
|
| 755 |
fn()
|
| 756 |
_sync()
|
| 757 |
+
# footgun probes: per-call allocation growth and hidden host work
|
| 758 |
+
alloc0 = torch.cuda.memory_allocated(device)
|
| 759 |
+
fn()
|
| 760 |
+
fn()
|
| 761 |
+
_sync()
|
| 762 |
+
alloc_growth = max(0, (torch.cuda.memory_allocated(device) - alloc0)
|
| 763 |
+
// 2)
|
| 764 |
+
# a burst of enqueues that leaves the stream idle means fn drains it; only trustworthy above the launch-overhead floor
|
| 765 |
+
for _ in range(16):
|
| 766 |
+
fn()
|
| 767 |
+
stream_idle = bool(torch.cuda.current_stream(device).query())
|
| 768 |
+
_sync()
|
| 769 |
+
t0 = time.perf_counter()
|
| 770 |
+
pre = _events(fn, 4)
|
| 771 |
+
wall_ms = (time.perf_counter() - t0) * 1e3
|
| 772 |
+
ev_ms = sum(pre)
|
| 773 |
+
host_suspect = (stream_idle and ev_ms / 4 > 0.05) or \
|
| 774 |
+
wall_ms > ev_ms * 1.5 + 2.0
|
| 775 |
+
|
| 776 |
graph_state = "off"
|
| 777 |
graph_reason = ""
|
| 778 |
+
if graph and graph_probe_isolated:
|
| 779 |
+
cap = probe_capturable(fn)
|
| 780 |
+
if cap["capturable"] is False:
|
| 781 |
+
graph = False
|
| 782 |
+
graph_state = "fallback_eager"
|
| 783 |
+
graph_reason = "isolated probe: " + cap["reason"]
|
| 784 |
if graph:
|
| 785 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 786 |
try:
|
|
|
|
| 795 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 796 |
graph_state = "fallback_eager"
|
| 797 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 798 |
+
# drain the invalidated stream; a failed capture can wedge the device RNG on some torch versions
|
|
|
|
|
|
|
|
|
|
| 799 |
rng_ok = True
|
| 800 |
for _attempt in range(5):
|
| 801 |
try:
|
|
|
|
| 825 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 826 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 827 |
best_clk = max(clocks)
|
| 828 |
+
|
| 829 |
+
# rejection basis: memory-bound work is judged on its own rate, not the SM clock
|
| 830 |
+
ridge_memory = False
|
| 831 |
+
if dossier is not None and bytes and flops:
|
| 832 |
+
bw0 = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 833 |
+
mma_v0 = dossier["mma"]["value"].get(dtype)
|
| 834 |
+
if isinstance(mma_v0, dict):
|
| 835 |
+
mma_v0 = mma_v0.get("dense_equivalent")
|
| 836 |
+
peak0 = (mma_v0 if isinstance(mma_v0, (int, float))
|
| 837 |
+
else dossier["fma_f32"]["value"]) * 1e12
|
| 838 |
+
ridge_memory = (flops / bytes) * bw0 < peak0
|
| 839 |
+
basis = "bandwidth_consistency" if (
|
| 840 |
+
bytes is not None and (flops is None or ridge_memory)) \
|
| 841 |
+
else "sm_clock"
|
| 842 |
+
|
| 843 |
flat = []
|
| 844 |
+
kept = []
|
| 845 |
+
rejected = 0
|
| 846 |
+
if basis == "sm_clock":
|
| 847 |
+
for group, ci in samples:
|
| 848 |
+
window_ok = (clocks[ci] >= clock_guard * best_clk and
|
| 849 |
+
clocks[min(ci + 1, len(clocks) - 1)] >=
|
| 850 |
+
clock_guard * best_clk)
|
| 851 |
+
for ms in group:
|
| 852 |
+
flat.append(ms)
|
| 853 |
+
if window_ok:
|
| 854 |
+
kept.append(ms)
|
| 855 |
+
else:
|
| 856 |
+
rejected += 1
|
| 857 |
+
else:
|
| 858 |
+
for group, _ci in samples:
|
| 859 |
+
flat.extend(group)
|
| 860 |
+
floor = sorted(flat)[max(0, int(0.05 * len(flat)) - 1)]
|
| 861 |
+
for ms in flat:
|
| 862 |
+
if ms <= floor * 1.15:
|
| 863 |
kept.append(ms)
|
| 864 |
else:
|
| 865 |
rejected += 1
|
| 866 |
+
all_rejected = not kept
|
| 867 |
+
if all_rejected:
|
| 868 |
kept = flat
|
| 869 |
med = _median(kept)
|
| 870 |
ks = sorted(kept)
|
| 871 |
n_k = len(ks)
|
| 872 |
+
# order-statistic 95% CI on the median; degenerate at tiny n
|
|
|
|
| 873 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 874 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 875 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
|
|
| 884 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 885 |
"samples": len(flat),
|
| 886 |
"rejected_throttled": rejected,
|
| 887 |
+
"rejection_basis": basis,
|
| 888 |
"graph_captured": graph_state == "captured",
|
| 889 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 890 |
"worst": round(min(clocks), 3)},
|
| 891 |
}
|
| 892 |
+
no_clean = all_rejected
|
| 893 |
+
if basis == "sm_clock" and dossier is not None:
|
| 894 |
+
ref_clk = dossier["clock"]["max_ghz"]
|
| 895 |
+
if best_clk < clock_guard * ref_clk:
|
| 896 |
+
no_clean = True
|
| 897 |
+
if no_clean:
|
| 898 |
+
rep["no_clean_window"] = True
|
| 899 |
if graph_reason:
|
| 900 |
rep["graph_fallback_reason"] = graph_reason
|
| 901 |
+
if alloc_growth:
|
| 902 |
+
rep["alloc_growth_bytes_per_call"] = int(alloc_growth)
|
| 903 |
+
if host_suspect:
|
| 904 |
+
rep["host_overhead_suspected"] = True
|
| 905 |
+
rep["host_wall_over_event"] = round(wall_ms / max(ev_ms, 1e-9), 2)
|
| 906 |
if bytes is not None:
|
| 907 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 908 |
if flops is not None:
|
|
|
|
| 912 |
if dossier is not None and bytes and flops:
|
| 913 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 914 |
mma_v = dossier["mma"]["value"].get(dtype)
|
| 915 |
+
if isinstance(mma_v, dict):
|
| 916 |
+
mma_v = mma_v.get("dense_equivalent")
|
| 917 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 918 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 919 |
ai = flops / bytes
|
|
|
|
| 924 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 925 |
"peak_dtype": dtype,
|
| 926 |
}
|
| 927 |
+
rep["verdict"] = _bench_verdict(rep)
|
| 928 |
return rep
|
| 929 |
|
| 930 |
|
| 931 |
def _to_ordered_int(t):
|
| 932 |
+
"""Monotone integer mapping of float bit patterns; differences are ULP distances."""
|
|
|
|
|
|
|
| 933 |
if t.dtype == torch.float32:
|
| 934 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 935 |
half = 1 << 31
|
|
|
|
| 945 |
|
| 946 |
|
| 947 |
def ulp_diff(a, b):
|
| 948 |
+
"""Elementwise ULP distance; both-NaN counts 0, one-sided NaN 2**62, integers by absolute difference."""
|
| 949 |
+
if a.dtype != b.dtype or a.shape != b.shape:
|
| 950 |
+
raise ValueError("ulp_diff needs matching dtype and shape; got "
|
| 951 |
+
f"{a.dtype}{tuple(a.shape)} vs "
|
| 952 |
+
f"{b.dtype}{tuple(b.shape)}; cast or reshape the "
|
| 953 |
+
"candidate to the reference layout")
|
| 954 |
if not a.is_floating_point():
|
| 955 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 956 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
|
|
| 967 |
|
| 968 |
@staticmethod
|
| 969 |
def last_dim():
|
| 970 |
+
"""Identical permutation of every arg's last dim: reduction order changes, value does not."""
|
|
|
|
|
|
|
| 971 |
def shuffle(args, seed):
|
| 972 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 973 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
|
|
| 977 |
|
| 978 |
@staticmethod
|
| 979 |
def matmul_k():
|
| 980 |
+
"""Consistent K permutation of (A [.., M, K], B [.., K, N]): product unchanged, accumulation order not."""
|
|
|
|
| 981 |
def shuffle(args, seed):
|
| 982 |
a_m, b_m = args[0], args[1]
|
| 983 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
|
|
| 991 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 992 |
|
| 993 |
|
| 994 |
+
def _compare_hints(trial):
|
| 995 |
+
hints = []
|
| 996 |
+
if trial.get("bias_one_sided_frac", 0) >= 0.9 and \
|
| 997 |
+
trial.get("frac_above_band", 0) > 0.5:
|
| 998 |
+
hints.append("systematic one-sided bias: check scale factors or "
|
| 999 |
+
"the accumulator dtype")
|
| 1000 |
+
if trial["nan_cand"] > trial["nan_ref"]:
|
| 1001 |
+
hints.append("candidate introduces NaN: check uninitialized "
|
| 1002 |
+
"memory or masking")
|
| 1003 |
+
nd = trial.get("first_divergent_nd")
|
| 1004 |
+
if nd and nd[-1] > 0 and any(nd[-1] % t == 0 for t in (16, 32, 64,
|
| 1005 |
+
128)):
|
| 1006 |
+
hints.append("divergence starts on a tile-aligned index: check "
|
| 1007 |
+
"boundary indexing")
|
| 1008 |
+
return hints
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def compare(ref, cand, gen=None, trials=8, order_shuffle=None,
|
| 1012 |
+
order_trials=8, ref_variants=None, ulp_tol=None, atol=None,
|
| 1013 |
+
rtol=None, band_margin=4.0, zero_atol=None, worst_k=8, seed=0,
|
| 1014 |
+
inputs=None, frac_above_tol=0.5):
|
| 1015 |
+
"""Adversarial correctness comparison with pointwise order bands, ULP evidence, and a one-sided-bias gate; the banded route cannot reject errors under band_margin*(band+2) ULP at order-stable elements."""
|
| 1016 |
+
if (gen is None) == (inputs is None):
|
| 1017 |
+
raise ValueError("pass exactly one of gen (a seed -> args "
|
| 1018 |
+
"callable) or inputs (a tuple of concrete "
|
| 1019 |
+
"tensors)")
|
| 1020 |
+
if inputs is not None:
|
| 1021 |
+
if isinstance(inputs, torch.Tensor):
|
| 1022 |
+
inputs = (inputs,)
|
| 1023 |
+
if not all(isinstance(x, torch.Tensor) for x in inputs):
|
| 1024 |
+
raise ValueError("inputs must be tensors; wrap scalars with "
|
| 1025 |
+
"torch.tensor first")
|
| 1026 |
+
fixed = tuple(inputs)
|
| 1027 |
+
gen = lambda seed: fixed # noqa: E731
|
| 1028 |
+
trials = 1
|
| 1029 |
worst = {"max_ulp": -1}
|
| 1030 |
total_hist = {}
|
| 1031 |
per_trial = []
|
| 1032 |
max_abs_seen = 0.0
|
| 1033 |
max_rel_seen = 0.0
|
| 1034 |
+
out_shapes = []
|
| 1035 |
for t in range(trials):
|
| 1036 |
args = gen(seed + t)
|
| 1037 |
+
if not isinstance(args, (tuple, list)):
|
| 1038 |
+
raise ValueError("gen(seed) must return a tuple of args; got "
|
| 1039 |
+
f"{type(args).__name__}; return (x,) for a "
|
| 1040 |
+
"single-argument op")
|
| 1041 |
r_outs = _as_outputs(ref(*args))
|
| 1042 |
c_outs = _as_outputs(cand(*args))
|
| 1043 |
+
if len(r_outs) != len(c_outs):
|
| 1044 |
+
raise ValueError(f"reference returned {len(r_outs)} outputs, "
|
| 1045 |
+
f"candidate {len(c_outs)}; make the arities "
|
| 1046 |
+
"match")
|
| 1047 |
+
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
| 1048 |
+
if r.dtype != c.dtype:
|
| 1049 |
+
raise ValueError(f"output {oi} dtype mismatch: ref "
|
| 1050 |
+
f"{r.dtype}, cand {c.dtype}; cast the "
|
| 1051 |
+
"candidate to the reference dtype")
|
| 1052 |
+
if r.shape != c.shape:
|
| 1053 |
+
raise ValueError(f"output {oi} shape mismatch: ref "
|
| 1054 |
+
f"{tuple(r.shape)}, cand "
|
| 1055 |
+
f"{tuple(c.shape)}")
|
| 1056 |
+
out_shapes = [tuple(r.shape) for r in r_outs]
|
| 1057 |
+
# pointwise band: each element judged against its own measured order sensitivity
|
| 1058 |
bands = None
|
| 1059 |
band = 0
|
| 1060 |
band_score = 0.0
|
|
|
|
| 1078 |
max_ulp = 0
|
| 1079 |
mean_ulp = 0.0
|
| 1080 |
first_div = -1
|
| 1081 |
+
first_div_nd = None
|
| 1082 |
n_el = 0
|
| 1083 |
n_above_band = 0
|
| 1084 |
+
n_diverging = 0
|
| 1085 |
+
n_cand_high = 0
|
| 1086 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 1087 |
worst_elements = []
|
| 1088 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
|
|
| 1100 |
mean_ulp += float(d.double().sum().item())
|
| 1101 |
n_el += d.numel()
|
| 1102 |
nz = (d > 0)
|
| 1103 |
+
n_diverging += int(nz.sum().item())
|
| 1104 |
+
n_cand_high += int(((c > r) & nz).sum().item())
|
| 1105 |
if first_div < 0 and nz.any():
|
| 1106 |
first_div = int(nz.flatten().nonzero()[0].item())
|
| 1107 |
+
first_div_nd = _unravel(first_div, r.shape)
|
| 1108 |
if nz.any():
|
| 1109 |
buckets = torch.clamp(
|
| 1110 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
|
|
| 1121 |
if val > 0:
|
| 1122 |
worst_elements.append({
|
| 1123 |
"output": oi, "index": int(ix),
|
| 1124 |
+
"index_nd": _unravel(int(ix), r.shape),
|
| 1125 |
"ref": float(rf[ix].item()),
|
| 1126 |
"cand": float(cf[ix].item()),
|
| 1127 |
"ulp": int(val)})
|
|
|
|
| 1141 |
inf_r += int(torch.isinf(r).sum().item())
|
| 1142 |
inf_c += int(torch.isinf(c).sum().item())
|
| 1143 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
| 1144 |
+
frac_pos = n_cand_high / max(n_diverging, 1)
|
| 1145 |
trial_rep = {
|
| 1146 |
"max_ulp": max_ulp,
|
| 1147 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 1148 |
"first_divergent_index": first_div,
|
| 1149 |
+
"first_divergent_nd": first_div_nd,
|
| 1150 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 1151 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 1152 |
"order_band_ulp": band,
|
| 1153 |
"band_score": round(band_score, 2),
|
| 1154 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
| 1155 |
+
"bias_one_sided_frac": round(max(frac_pos, 1 - frac_pos), 4)
|
| 1156 |
+
if n_diverging else 0.0,
|
| 1157 |
"worst_elements": worst_elements[:worst_k],
|
| 1158 |
}
|
| 1159 |
per_trial.append(trial_rep)
|
| 1160 |
if max_ulp > worst["max_ulp"]:
|
| 1161 |
worst = trial_rep
|
|
|
|
| 1162 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 1163 |
+
banded = order_shuffle is not None or ref_variants
|
| 1164 |
+
if banded:
|
| 1165 |
score = max(t["band_score"] for t in per_trial)
|
| 1166 |
ulp_ok = score <= band_margin
|
| 1167 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
|
|
| 1176 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 1177 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 1178 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 1179 |
+
# one-sided bias gate: a uniform small error is above-band nearly everywhere in one direction
|
| 1180 |
+
bias_fail = False
|
| 1181 |
+
if banded and (ulp_ok or tol_ok):
|
| 1182 |
+
w_frac = max(t["frac_above_band"] for t in per_trial)
|
| 1183 |
+
w_side = max(t["bias_one_sided_frac"] for t in per_trial)
|
| 1184 |
+
if w_frac > frac_above_tol and w_side >= 0.9:
|
| 1185 |
+
bias_fail = True
|
| 1186 |
+
criterion += ("; FAILED bias gate: frac_above_band %.3f > %s "
|
| 1187 |
+
"with one-sided fraction %.2f" %
|
| 1188 |
+
(w_frac, frac_above_tol, w_side))
|
| 1189 |
+
passed = bool((ulp_ok or tol_ok) and not bias_fail)
|
| 1190 |
+
hints = [] if passed else _compare_hints(worst)
|
| 1191 |
+
if passed:
|
| 1192 |
+
verdict = "pass: " + (
|
| 1193 |
+
f"max {max_seen} ULP within the order band" if banded
|
| 1194 |
+
else f"max {max_seen} ULP")
|
| 1195 |
+
elif bias_fail:
|
| 1196 |
+
verdict = ("fail: uniform bias, %.0f%% of elements above their "
|
| 1197 |
+
"order band in one direction" %
|
| 1198 |
+
(100 * max(t["frac_above_band"] for t in per_trial)))
|
| 1199 |
+
else:
|
| 1200 |
+
w0 = worst["worst_elements"][0] if worst["worst_elements"] else None
|
| 1201 |
+
verdict = "fail: max %d ULP" % max_seen + (
|
| 1202 |
+
" at index %s (ref %.6g, cand %.6g)" %
|
| 1203 |
+
(w0["index_nd"], w0["ref"], w0["cand"]) if w0 else "")
|
| 1204 |
+
rep = {
|
| 1205 |
+
"pass": passed,
|
| 1206 |
+
"verdict": verdict,
|
| 1207 |
"criterion": criterion,
|
| 1208 |
"worst_trial": worst,
|
| 1209 |
"max_abs": max_abs_seen,
|
|
|
|
| 1211 |
"trials": per_trial,
|
| 1212 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 1213 |
}
|
| 1214 |
+
if hints:
|
| 1215 |
+
rep["hints"] = hints
|
| 1216 |
+
return rep
|
| 1217 |
|
| 1218 |
|
| 1219 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
|
|
| 1229 |
return False
|
| 1230 |
|
| 1231 |
|
| 1232 |
+
class _RandnMaker:
|
| 1233 |
+
"""Picklable default maker: `arity` same-shape randn tensors (zeros for empty shapes)."""
|
| 1234 |
+
|
| 1235 |
+
def __init__(self, arity=1):
|
| 1236 |
+
self.arity = arity
|
| 1237 |
+
|
| 1238 |
+
def __call__(self, shape, dtype=torch.float32):
|
| 1239 |
+
n = math.prod(shape)
|
| 1240 |
+
if n:
|
| 1241 |
+
out = tuple(torch.randn(tuple(shape), device="cuda",
|
| 1242 |
+
dtype=dtype)
|
| 1243 |
+
for _ in range(self.arity))
|
| 1244 |
+
else:
|
| 1245 |
+
out = tuple(torch.zeros(tuple(shape), device="cuda",
|
| 1246 |
+
dtype=dtype)
|
| 1247 |
+
for _ in range(self.arity))
|
| 1248 |
+
return out if self.arity > 1 else out[0]
|
| 1249 |
+
|
| 1250 |
+
|
| 1251 |
+
def _default_make(arity=1):
|
| 1252 |
+
"""Default maker instance; a class so isolate=True can transport it."""
|
| 1253 |
+
return _RandnMaker(arity)
|
| 1254 |
+
|
| 1255 |
+
|
| 1256 |
def _fuzz_build(make, shape, dtype):
|
| 1257 |
try:
|
| 1258 |
out = make(tuple(shape), dtype)
|
|
|
|
| 1269 |
if v == "strided_rows":
|
| 1270 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 1271 |
if v == "offset_slice":
|
| 1272 |
+
# storage-offset-1 view: misaligned for vectorized loads
|
| 1273 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 1274 |
if v == "overflow_expand":
|
| 1275 |
if x.numel() == 0:
|
|
|
|
| 1283 |
|
| 1284 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 1285 |
stop_on_context_loss=True):
|
| 1286 |
+
"""All variant cases for one (shape, dtype) group; returns (rows, context_lost)."""
|
|
|
|
| 1287 |
rows = []
|
| 1288 |
bases = _fuzz_build(make, shape, dtype)
|
| 1289 |
lost = False
|
|
|
|
| 1320 |
got = op(*xs)
|
| 1321 |
want = ref(*xs)
|
| 1322 |
if v == "overflow_expand":
|
| 1323 |
+
# bounded comparison: head plus the high-index tail where int32 indexing wraps
|
|
|
|
| 1324 |
w = 1 << 20
|
| 1325 |
gf, wf = got.flatten(), want.flatten()
|
| 1326 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
|
|
| 1345 |
return rows, lost
|
| 1346 |
|
| 1347 |
|
| 1348 |
+
def _fn_spec(fn, name):
|
| 1349 |
+
"""Picklable transport spec for a callable, with a source fallback for local defs."""
|
| 1350 |
+
import pickle
|
| 1351 |
+
try:
|
| 1352 |
+
return ("pickle", pickle.dumps(fn), None)
|
| 1353 |
+
except Exception: # noqa: BLE001
|
| 1354 |
+
import inspect
|
| 1355 |
+
import textwrap
|
| 1356 |
+
try:
|
| 1357 |
+
src = textwrap.dedent(inspect.getsource(fn))
|
| 1358 |
+
except (OSError, TypeError):
|
| 1359 |
+
src = ""
|
| 1360 |
+
if not src.startswith("def "):
|
| 1361 |
+
raise ValueError(
|
| 1362 |
+
f"{name} is not picklable and not a plain def; define "
|
| 1363 |
+
"op/ref/make as top-level functions (not lambdas or "
|
| 1364 |
+
"closures) to use isolate=True") from None
|
| 1365 |
+
return ("source", src, fn.__name__)
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
_POOL_BOOTSTRAP = r"""
|
| 1369 |
+
import json, os, pickle, sys
|
| 1370 |
meta = json.load(open(sys.argv[1], "r"))
|
| 1371 |
+
sys.path[:0] = meta["sys_path"]
|
| 1372 |
try:
|
| 1373 |
import kernels
|
| 1374 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
|
|
| 1381 |
import load_local
|
| 1382 |
_mk = load_local.load()
|
| 1383 |
import torch
|
| 1384 |
+
_ns = {"torch": torch}
|
| 1385 |
+
def _resolve(spec):
|
| 1386 |
+
kind, payload, name = spec
|
| 1387 |
+
if kind == "pickle":
|
| 1388 |
+
return pickle.loads(payload)
|
| 1389 |
+
exec(payload, _ns)
|
| 1390 |
+
return _ns[name]
|
| 1391 |
+
_op, _ref, _make = (_resolve(s) for s in
|
| 1392 |
+
pickle.load(open(meta["fns"], "rb")))
|
| 1393 |
+
print("ready", flush=True)
|
| 1394 |
+
for line in sys.stdin:
|
| 1395 |
+
job = json.loads(line)
|
| 1396 |
+
rows, _l = _mk._fuzz_group(
|
| 1397 |
+
_op, _ref, _make, tuple(job["shape"]),
|
| 1398 |
+
getattr(torch, job["dtype"]), tuple(job["variants"]),
|
| 1399 |
+
job["ulp_tol"], job["device"], stop_on_context_loss=False)
|
| 1400 |
+
pickle.dump(rows, open(job["out"], "wb"))
|
| 1401 |
+
print("done", flush=True)
|
| 1402 |
"""
|
| 1403 |
|
| 1404 |
|
| 1405 |
+
class _FuzzPool:
|
| 1406 |
+
"""One persistent isolation worker fed groups over stdin, respawned only after a crash."""
|
| 1407 |
+
|
| 1408 |
+
def __init__(self, meta_path, stderr_path, timeout_s):
|
| 1409 |
+
self.meta_path = meta_path
|
| 1410 |
+
self.stderr_path = stderr_path
|
| 1411 |
+
self.timeout_s = timeout_s
|
| 1412 |
+
self.proc = None
|
| 1413 |
+
self.q = None
|
| 1414 |
+
|
| 1415 |
+
def _spawn(self):
|
| 1416 |
+
import queue
|
| 1417 |
+
import subprocess
|
| 1418 |
+
import sys as _sys
|
| 1419 |
+
import threading
|
| 1420 |
+
self.errf = open(self.stderr_path, "ab")
|
| 1421 |
+
self.proc = subprocess.Popen(
|
| 1422 |
+
[_sys.executable, "-c", _POOL_BOOTSTRAP, self.meta_path],
|
| 1423 |
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
| 1424 |
+
stderr=self.errf, text=True)
|
| 1425 |
+
self.q = queue.Queue()
|
| 1426 |
+
|
| 1427 |
+
def reader(p, q):
|
| 1428 |
+
for line in p.stdout:
|
| 1429 |
+
q.put(line.strip())
|
| 1430 |
+
threading.Thread(target=reader, args=(self.proc, self.q),
|
| 1431 |
+
daemon=True).start()
|
| 1432 |
+
try:
|
| 1433 |
+
first = self.q.get(timeout=self.timeout_s)
|
| 1434 |
+
except Exception: # noqa: BLE001
|
| 1435 |
+
first = None
|
| 1436 |
+
return first == "ready"
|
| 1437 |
+
|
| 1438 |
+
def run(self, job_line):
|
| 1439 |
+
"""Returns True when the worker reports done, False on crash or timeout."""
|
| 1440 |
+
import queue
|
| 1441 |
+
if self.proc is None or self.proc.poll() is not None:
|
| 1442 |
+
if not self._spawn():
|
| 1443 |
+
return False
|
| 1444 |
+
try:
|
| 1445 |
+
self.proc.stdin.write(job_line + "\n")
|
| 1446 |
+
self.proc.stdin.flush()
|
| 1447 |
+
except OSError:
|
| 1448 |
+
return False
|
| 1449 |
+
try:
|
| 1450 |
+
return self.q.get(timeout=self.timeout_s) == "done"
|
| 1451 |
+
except queue.Empty:
|
| 1452 |
+
if self.proc.poll() is None:
|
| 1453 |
+
self.proc.kill()
|
| 1454 |
+
return False
|
| 1455 |
+
|
| 1456 |
+
def stderr_tail(self):
|
| 1457 |
+
try:
|
| 1458 |
+
self.errf.flush()
|
| 1459 |
+
with open(self.stderr_path, "rb") as f:
|
| 1460 |
+
return f.read()[-400:].decode(errors="replace")
|
| 1461 |
+
except OSError:
|
| 1462 |
+
return ""
|
| 1463 |
+
|
| 1464 |
+
def close(self):
|
| 1465 |
+
if self.proc is not None and self.proc.poll() is None:
|
| 1466 |
+
self.proc.kill()
|
| 1467 |
+
try:
|
| 1468 |
+
self.errf.close()
|
| 1469 |
+
except Exception: # noqa: BLE001
|
| 1470 |
+
pass
|
| 1471 |
+
|
| 1472 |
+
|
| 1473 |
+
def _fuzz_hints(rows):
|
| 1474 |
+
hints = []
|
| 1475 |
+
bad = [c for c in rows if c["status"] == "mismatch"]
|
| 1476 |
+
if bad and all(c["variant"] in ("transposed", "strided_rows",
|
| 1477 |
+
"offset_slice") for c in bad):
|
| 1478 |
+
hints.append("mismatches only on non-contiguous variants: the op "
|
| 1479 |
+
"assumes contiguity; call .contiguous() or honor "
|
| 1480 |
+
"strides")
|
| 1481 |
+
if any(c["variant"] == "overflow_expand" and
|
| 1482 |
+
c["status"] == "mismatch" for c in rows):
|
| 1483 |
+
hints.append("overflow_expand mismatch: int32 indexing wraps past "
|
| 1484 |
+
"2^31 elements; use 64-bit index arithmetic")
|
| 1485 |
+
return hints
|
| 1486 |
+
|
| 1487 |
+
|
| 1488 |
+
def fuzz_shapes(op, ref, make=None, shapes=None, variants=_FUZZ_VARIANTS,
|
| 1489 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 1490 |
+
isolate_timeout_s=180, device="cuda", arity=1):
|
| 1491 |
+
"""Edge shapes, dtypes, and layout mutations with per-case repro recipes; isolate=True survives context-killing candidates via a pooled worker."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1492 |
if shapes is None:
|
| 1493 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1494 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
| 1495 |
+
if make is None:
|
| 1496 |
+
make = _default_make(arity)
|
| 1497 |
|
| 1498 |
rows = []
|
| 1499 |
lost = False
|
| 1500 |
if isolate:
|
| 1501 |
import json
|
| 1502 |
import pickle
|
|
|
|
| 1503 |
import sys as _sys
|
| 1504 |
import tempfile
|
| 1505 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1506 |
fns = os.path.join(tmpdir, "fns.pkl")
|
| 1507 |
+
specs = [_fn_spec(f, n) for f, n in ((op, "op"), (ref, "ref"),
|
| 1508 |
+
(make, "make"))]
|
| 1509 |
with open(fns, "wb") as f:
|
| 1510 |
+
pickle.dump(specs, f)
|
| 1511 |
+
meta_path = os.path.join(tmpdir, "meta.json")
|
| 1512 |
+
with open(meta_path, "w", encoding="utf-8") as f:
|
| 1513 |
+
json.dump({"sys_path": [p for p in _sys.path
|
| 1514 |
+
if isinstance(p, str)],
|
| 1515 |
+
"repo": _REPO_ID, "version": _REPO_VERSION,
|
| 1516 |
+
"fns": fns}, f)
|
| 1517 |
+
pool = _FuzzPool(meta_path, os.path.join(tmpdir, "stderr.log"),
|
| 1518 |
+
isolate_timeout_s)
|
| 1519 |
+
try:
|
| 1520 |
+
for dtype in dtypes:
|
| 1521 |
+
for shape in shapes:
|
| 1522 |
+
out = os.path.join(tmpdir, "rows.pkl")
|
| 1523 |
+
if os.path.exists(out):
|
| 1524 |
+
os.remove(out)
|
| 1525 |
+
job = json.dumps({
|
| 1526 |
"shape": list(shape),
|
| 1527 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1528 |
"variants": list(variants),
|
| 1529 |
"ulp_tol": ulp_tol, "device": device,
|
| 1530 |
+
"out": out})
|
| 1531 |
+
ok = pool.run(job)
|
| 1532 |
+
if ok and os.path.exists(out):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
with open(out, "rb") as f:
|
| 1534 |
+
rows.extend(pickle.load(f))
|
| 1535 |
+
else:
|
| 1536 |
+
rows.append({
|
| 1537 |
+
"shape": tuple(shape), "variant": "*",
|
| 1538 |
+
"dtype": str(dtype).replace("torch.", ""),
|
| 1539 |
+
"status": "crashed_isolated",
|
| 1540 |
+
"stderr_tail": pool.stderr_tail()})
|
| 1541 |
+
finally:
|
| 1542 |
+
pool.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1543 |
else:
|
| 1544 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1545 |
for gi, (dtype, shape) in enumerate(pending):
|
|
|
|
| 1556 |
n_bad = sum(1 for r in rows
|
| 1557 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1558 |
"skipped_empty"))
|
| 1559 |
+
rep = {"cases": rows, "failed": n_bad, "total": len(rows),
|
| 1560 |
+
"context_lost": lost}
|
| 1561 |
+
hints = _fuzz_hints(rows)
|
| 1562 |
+
if hints:
|
| 1563 |
+
rep["hints"] = hints
|
| 1564 |
+
if n_bad == 0:
|
| 1565 |
+
rep["verdict"] = "pass: %d/%d cases ok" % (len(rows) - n_bad,
|
| 1566 |
+
len(rows))
|
| 1567 |
+
else:
|
| 1568 |
+
worst_case = next(c for c in rows
|
| 1569 |
+
if c["status"] not in ("ok", "skipped_vram",
|
| 1570 |
+
"skipped_empty"))
|
| 1571 |
+
rep["verdict"] = "fail: %d/%d cases (%s at %s %s)" % (
|
| 1572 |
+
n_bad, len(rows), worst_case["status"], worst_case["shape"],
|
| 1573 |
+
worst_case["variant"])
|
| 1574 |
+
return rep
|
| 1575 |
|
| 1576 |
|
| 1577 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1578 |
check=None, budget_s=None, race=False):
|
| 1579 |
+
"""Launch-configuration search, fastest first, with a correctness gate per row and no silently dropped points."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1580 |
keys = list(grid.keys())
|
| 1581 |
|
| 1582 |
def expand(i, acc):
|
|
|
|
| 1641 |
continue
|
| 1642 |
rows.append(run_point(p, iters))
|
| 1643 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
| 1644 |
+
if rows:
|
| 1645 |
+
n_pruned = sum(1 for r in rows if r.get("status") == "pruned_race")
|
| 1646 |
+
n_skip = sum(1 for r in rows if r.get("status") == "not_run")
|
| 1647 |
+
best = rows[0]
|
| 1648 |
+
rows_verdict = "fastest %s at %.4g ms" % (
|
| 1649 |
+
{k: best[k] for k in keys}, best["median_ms"])
|
| 1650 |
+
if n_pruned:
|
| 1651 |
+
rows_verdict += "; %d pruned" % n_pruned
|
| 1652 |
+
if n_skip:
|
| 1653 |
+
rows_verdict += "; %d not run" % n_skip
|
| 1654 |
+
best["verdict"] = rows_verdict
|
| 1655 |
return rows
|
| 1656 |
|
| 1657 |
|
| 1658 |
+
def judge(cand, ref, inputs=None, gen=None, bytes=None, flops=None,
|
| 1659 |
+
dtype=None, order_shuffle=None, make=None, quick_fuzz=True,
|
| 1660 |
+
device="cuda"):
|
| 1661 |
+
"""One-call verdict: compare, a quick fuzz, and a roofline bench against the cached dossier."""
|
| 1662 |
+
if (gen is None) == (inputs is None):
|
| 1663 |
+
raise ValueError("pass exactly one of inputs (concrete tensors) "
|
| 1664 |
+
"or gen (seed -> args)")
|
| 1665 |
+
if inputs is not None and isinstance(inputs, torch.Tensor):
|
| 1666 |
+
inputs = (inputs,)
|
| 1667 |
+
args0 = tuple(inputs) if inputs is not None else gen(0)
|
| 1668 |
+
arity = len(args0)
|
| 1669 |
+
if dtype is None:
|
| 1670 |
+
dt0 = args0[0].dtype
|
| 1671 |
+
dtype = {torch.float16: "fp16", torch.bfloat16: "bf16"}.get(
|
| 1672 |
+
dt0, "f32")
|
| 1673 |
+
dossier = probe_device(device, quick=True, cached=True,
|
| 1674 |
+
tenancy_check=False)
|
| 1675 |
+
cmp_rep = compare(ref, cand, gen=gen, inputs=inputs, trials=4,
|
| 1676 |
+
order_shuffle=order_shuffle)
|
| 1677 |
+
fuzz_rep = None
|
| 1678 |
+
if quick_fuzz:
|
| 1679 |
+
fuzz_rep = fuzz_shapes(
|
| 1680 |
+
cand, ref, make=make, arity=arity,
|
| 1681 |
+
shapes=[(1, 17), (3, 63), (128, 128)],
|
| 1682 |
+
variants=("contig", "transposed", "offset_slice"),
|
| 1683 |
+
dtypes=(args0[0].dtype,))
|
| 1684 |
+
bench_rep = None
|
| 1685 |
+
if inputs is not None:
|
| 1686 |
+
bench_rep = bench(lambda: cand(*inputs), iters=32, warmup=6,
|
| 1687 |
+
bytes=bytes, flops=flops, dossier=dossier,
|
| 1688 |
+
dtype=dtype, device=device)
|
| 1689 |
+
passed = cmp_rep["pass"] and (fuzz_rep is None or
|
| 1690 |
+
fuzz_rep["failed"] == 0)
|
| 1691 |
+
parts = [cmp_rep["verdict"]]
|
| 1692 |
+
if fuzz_rep is not None:
|
| 1693 |
+
parts.append(fuzz_rep["verdict"])
|
| 1694 |
+
if bench_rep is not None:
|
| 1695 |
+
parts.append(bench_rep["verdict"])
|
| 1696 |
+
rep = {"pass": passed, "verdict" : " | ".join(parts),
|
| 1697 |
+
"compare": cmp_rep, "fuzz": fuzz_rep, "bench": bench_rep,
|
| 1698 |
+
"device": dossier["device"]["name"]}
|
| 1699 |
+
hints = list(cmp_rep.get("hints", []))
|
| 1700 |
+
if fuzz_rep is not None:
|
| 1701 |
+
hints += fuzz_rep.get("hints", [])
|
| 1702 |
+
if hints:
|
| 1703 |
+
rep["hints"] = hints
|
| 1704 |
+
return rep
|
| 1705 |
+
|
| 1706 |
+
|
| 1707 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1708 |
+
"""Device buffer for MK_STAMP (1-D) or MK_STAMP_BLOCK ([blocks, n]) stamps."""
|
|
|
|
|
|
|
| 1709 |
if blocks is not None:
|
| 1710 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1711 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
|
|
| 1715 |
|
| 1716 |
|
| 1717 |
def pinned_dossiers():
|
| 1718 |
+
"""Names of the release-pinned dossiers, one per certified architecture."""
|
|
|
|
| 1719 |
return sorted(_PINNED.keys())
|
| 1720 |
|
| 1721 |
|
|
|
|
| 1734 |
|
| 1735 |
|
| 1736 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1737 |
+
"""Per-leaf relative drift of a fresh dossier against the release pin for the same architecture."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1738 |
if pinned is None:
|
| 1739 |
arch = current["device"]["sm_arch"]
|
| 1740 |
matches = [k for k, v in _PINNED.items()
|
|
|
|
| 1757 |
"rel": round(rel, 4)})
|
| 1758 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1759 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1760 |
+
rep = {"entries": entries, "worst_rel": worst, "over_tol": over,
|
| 1761 |
+
"rel_tol": rel_tol, "compared": len(entries),
|
| 1762 |
+
"pass": len(over) == 0}
|
| 1763 |
+
rep["verdict"] = ("pass: worst drift %.3f over %d entries" %
|
| 1764 |
+
(worst, len(entries))) if rep["pass"] else \
|
| 1765 |
+
("fail: %d entries over %.2f, worst %s at %.3f" %
|
| 1766 |
+
(len(over), rel_tol, over[0]["path"], worst))
|
| 1767 |
+
return rep
|
| 1768 |
|
| 1769 |
|
| 1770 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1771 |
device="cuda"):
|
| 1772 |
+
"""Run the shipped MK_STAMP_BLOCK example (load, compute, drained store) and decode its stamps."""
|
|
|
|
|
|
|
| 1773 |
props = torch.cuda.get_device_properties(device)
|
| 1774 |
if blocks is None:
|
| 1775 |
blocks = props.multi_processor_count
|
|
|
|
| 1782 |
|
| 1783 |
|
| 1784 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1785 |
+
"""Decode MK_STAMP deltas to ticks and microseconds; 2-D buffers yield min/median/max across blocks."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1786 |
if clock_ghz is None:
|
| 1787 |
clock_ghz = measure_clock(stamps.device)
|
| 1788 |
|
|
|
|
| 1818 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1819 |
"blocks_reporting": int(live.sum().item()),
|
| 1820 |
"blocks_total": int(live.numel())}
|
| 1821 |
+
|
| 1822 |
+
|
| 1823 |
+
_SCHEMAS = {
|
| 1824 |
+
"probe_device": {
|
| 1825 |
+
"device": "name, sm_arch, sms, l2_bytes, vram_bytes, torch, "
|
| 1826 |
+
"cuda_runtime, ecc, driver, power_limit_w",
|
| 1827 |
+
"clock": "max/min/final GHz under sustained FMA load plus sag_pct",
|
| 1828 |
+
"dram_bw": "GB/s: triad_4B/8B/16B, read_only, write_only, "
|
| 1829 |
+
"random_gather_16B",
|
| 1830 |
+
"l2_bw": "GB/s over an L2-resident working set",
|
| 1831 |
+
"latency": "cycles: smem, l1, l2, dram (min of 3 chases) plus "
|
| 1832 |
+
"ns_per_cycle",
|
| 1833 |
+
"fma_f32 / fma_f64": "TFLOP/s of the register-resident pipes",
|
| 1834 |
+
"fma_ilp": "TFLOP/s at 1/2/4 chains plus ratio_4_1; near 4 means "
|
| 1835 |
+
"latency-bound",
|
| 1836 |
+
"mma": "TFLOP/s per dtype; sparse carries executed and "
|
| 1837 |
+
"dense_equivalent; verified_k_lanes carries "
|
| 1838 |
+
"executed_k_per_iter, vs_nominal, calibration_linear; "
|
| 1839 |
+
"fp8_accum carries the accumulate-precision stall",
|
| 1840 |
+
"mma_fed": "TFLOP/s of smem-staged fp16 tiles (staging exposed)",
|
| 1841 |
+
"smem_bw": "GB/s aggregate, conflict-light pattern",
|
| 1842 |
+
"smem_conflict_penalty": "time ratio vs stride 1 per lane stride",
|
| 1843 |
+
"transfer_bw": "GB/s: h2d/d2h pinned and pageable, d2d",
|
| 1844 |
+
"atomics_f32": "Gop/s curve over distinct addresses plus "
|
| 1845 |
+
"shared_same_addr",
|
| 1846 |
+
"launch_overhead": "us: torch_queued, torch_round_trip, "
|
| 1847 |
+
"raw_queued, raw_round_trip",
|
| 1848 |
+
"grid_barrier": "us per grid.sync or 'unsupported'",
|
| 1849 |
+
"occupancy_blocks_per_sm": "API table by (threads, dyn smem)",
|
| 1850 |
+
"occupancy_knees_fma_tflops": "timed dependent-chain table",
|
| 1851 |
+
},
|
| 1852 |
+
"bench": {
|
| 1853 |
+
"median_ms": "median over accepted samples",
|
| 1854 |
+
"sustained_ms": "median over all samples",
|
| 1855 |
+
"median_ci95_ms / p05_ms / p95_ms / min_ms / mean_ms":
|
| 1856 |
+
"distribution of accepted samples",
|
| 1857 |
+
"samples / rejected_throttled / rejection_basis":
|
| 1858 |
+
"counts and whether rejection used sm_clock or "
|
| 1859 |
+
"bandwidth_consistency",
|
| 1860 |
+
"no_clean_window": "present when every window ran throttled; the "
|
| 1861 |
+
"median is then a throttled-regime number",
|
| 1862 |
+
"clock_ghz": "best and worst SM clock probes around the run",
|
| 1863 |
+
"achieved_gbs / achieved_tflops / arithmetic_intensity":
|
| 1864 |
+
"rates from declared traffic",
|
| 1865 |
+
"roofline": "bound, ridge_flops_per_byte, pct_of_roof, peak_dtype",
|
| 1866 |
+
"graph_captured / graph_fallback_reason": "CUDA-graph state",
|
| 1867 |
+
"alloc_growth_bytes_per_call": "present when fn leaks allocations",
|
| 1868 |
+
"host_overhead_suspected": "present when fn syncs or does host "
|
| 1869 |
+
"work inside; ops under ~50 us are "
|
| 1870 |
+
"below the detection floor",
|
| 1871 |
+
"verdict": "one-sentence summary",
|
| 1872 |
+
},
|
| 1873 |
+
"compare": {
|
| 1874 |
+
"pass": "final verdict incl. the one-sided bias gate",
|
| 1875 |
+
"verdict / criterion / hints": "summary, acceptance rule, and "
|
| 1876 |
+
"failure-pattern suggestions",
|
| 1877 |
+
"worst_trial": "max_ulp, mean_ulp, first_divergent_index(+_nd), "
|
| 1878 |
+
"NaN/Inf census, order_band_ulp, band_score, "
|
| 1879 |
+
"frac_above_band, bias_one_sided_frac, "
|
| 1880 |
+
"worst_elements (index, index_nd, ref, cand, ulp)",
|
| 1881 |
+
"max_abs / max_rel / ulp_hist_log2": "aggregate evidence",
|
| 1882 |
+
"floor": "the banded route cannot reject errors under "
|
| 1883 |
+
"band_margin*(band+2) ULP at order-stable elements",
|
| 1884 |
+
},
|
| 1885 |
+
"fuzz_shapes": {
|
| 1886 |
+
"cases": "per-case shape, variant, dtype, status, repro recipe",
|
| 1887 |
+
"status values": "ok | mismatch | exception | context_killed | "
|
| 1888 |
+
"context_lost | crashed_isolated | skipped_vram "
|
| 1889 |
+
"| skipped_empty",
|
| 1890 |
+
"failed / total / context_lost / verdict / hints": "aggregates",
|
| 1891 |
+
},
|
| 1892 |
+
"sweep": {
|
| 1893 |
+
"rows": "grid point plus median_ms, rejected_throttled, check?, "
|
| 1894 |
+
"achieved rates, status in pruned_race | not_run",
|
| 1895 |
+
"verdict": "on the fastest row",
|
| 1896 |
+
},
|
| 1897 |
+
"dossier_drift": {
|
| 1898 |
+
"entries": "path, current, pinned, rel per intersecting leaf",
|
| 1899 |
+
"worst_rel / over_tol / compared / pass / verdict": "aggregates",
|
| 1900 |
+
},
|
| 1901 |
+
"judge": {
|
| 1902 |
+
"pass / verdict / hints": "composite over compare, fuzz, bench",
|
| 1903 |
+
"compare / fuzz / bench": "the full sub-reports",
|
| 1904 |
+
},
|
| 1905 |
+
"read_stamps": {
|
| 1906 |
+
"phases": "ticks and us per phase; 2-D input adds min/median/max "
|
| 1907 |
+
"across blocks",
|
| 1908 |
+
"note": "stamps record issue; DRAIN variants include the store "
|
| 1909 |
+
"drain to the coherence point",
|
| 1910 |
+
},
|
| 1911 |
+
}
|
| 1912 |
+
|
| 1913 |
+
|
| 1914 |
+
def schema(name=None):
|
| 1915 |
+
"""Field layout of every report, so an agent can look up what a number means."""
|
| 1916 |
+
if name is None:
|
| 1917 |
+
return _SCHEMAS
|
| 1918 |
+
if name not in _SCHEMAS:
|
| 1919 |
+
raise KeyError(f"no schema for {name!r}; have "
|
| 1920 |
+
f"{sorted(_SCHEMAS)}")
|
| 1921 |
+
return _SCHEMAS[name]
|
build/torch212-cxx11-cu126-x86_64-linux/{_metakernel_cuda_b62a1ce.abi3.so → _metakernel_cuda_0b9de42.abi3.so}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b143b103b2bf8dfa9129c62d7ef7b33155749eea06f7f0f3ee009a8e5e7736c5
|
| 3 |
+
size 900560
|
build/torch212-cxx11-cu126-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _metakernel_cuda_0b9de42
|
| 3 |
+
ops = torch.ops._metakernel_cuda_0b9de42
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_metakernel_cuda_0b9de42::{op_name}"
|
build/torch212-cxx11-cu126-x86_64-linux/_pinned.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
Generated by dev/pin_dossier.py from full probe_device() runs on idle,
|
| 4 |
-
single-tenant cards; consumed by metakernel.dossier_drift.
|
| 5 |
-
"""
|
| 6 |
|
| 7 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 8 |
'confidence': 'high',
|
|
@@ -295,7 +291,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 295 |
'd2h_pinned': 10.8,
|
| 296 |
'h2d_pageable': 10.3,
|
| 297 |
'h2d_pinned': 11.8}}},
|
| 298 |
-
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.
|
| 299 |
'confidence': 'high',
|
| 300 |
'method': 'atomicAdd '
|
| 301 |
'hammer; curve '
|
|
@@ -308,21 +304,21 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 308 |
'contention) to '
|
| 309 |
'one per thread',
|
| 310 |
'units': 'Gop/s',
|
| 311 |
-
'value': {'curve_slots': {'1': 0.
|
| 312 |
-
'145408':
|
| 313 |
-
'4096':
|
| 314 |
-
'512':
|
| 315 |
-
'64': 13.
|
| 316 |
-
'8': 5.
|
| 317 |
-
'shared_same_addr': 12.
|
| 318 |
'clock': {'confidence': 'high',
|
| 319 |
-
'final_ghz': 2.
|
| 320 |
-
'max_ghz': 2.
|
| 321 |
'method': '24 back-to-back FMA '
|
| 322 |
'spin windows of 30M '
|
| 323 |
'ticks; wall/ticks '
|
| 324 |
'per window',
|
| 325 |
-
'min_ghz': 2.
|
| 326 |
'sag_pct': 0.3,
|
| 327 |
'units': 'GHz'},
|
| 328 |
'device': {'cuda_runtime': '12.6',
|
|
@@ -336,7 +332,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 336 |
'sms': 142,
|
| 337 |
'torch': '2.10.0+cu126',
|
| 338 |
'vram_bytes': 48305274880},
|
| 339 |
-
'dram_bw': {'clock_ghz': 2.
|
| 340 |
'confidence': 'high',
|
| 341 |
'method': 'streaming triad '
|
| 342 |
'c=a+s*b per access '
|
|
@@ -356,26 +352,41 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 356 |
'line-efficiency '
|
| 357 |
'claim',
|
| 358 |
'units': 'GB/s',
|
| 359 |
-
'value': {'random_gather_16B': 218.
|
| 360 |
-
'read_only':
|
| 361 |
-
'triad_16B':
|
| 362 |
-
'triad_4B':
|
| 363 |
-
'triad_8B':
|
| 364 |
'write_only': 766.5}},
|
| 365 |
-
'fma_f32': {'clock_ghz': 2.
|
| 366 |
'confidence': 'high',
|
| 367 |
'method': 'register-resident '
|
| 368 |
'FMA chains',
|
| 369 |
'units': 'TFLOP/s',
|
| 370 |
-
'value':
|
| 371 |
-
'fma_f64': {'clock_ghz': 2.
|
| 372 |
'confidence': 'high',
|
| 373 |
'method': 'register-resident '
|
| 374 |
'double FMA chains '
|
| 375 |
'(the DFMA pipe)',
|
| 376 |
'units': 'TFLOP/s',
|
| 377 |
-
'value': 1.
|
| 378 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
'confidence': 'high',
|
| 380 |
'method': 'cooperative '
|
| 381 |
'kernel, 142 '
|
|
@@ -383,8 +394,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 383 |
'threads',
|
| 384 |
'units': 'us per '
|
| 385 |
'grid.sync',
|
| 386 |
-
'value': 0.
|
| 387 |
-
'l2_bw': {'clock_ghz': 2.
|
| 388 |
'confidence': 'medium',
|
| 389 |
'method': 'in-kernel repeated '
|
| 390 |
'triad passes over 72 '
|
|
@@ -394,14 +405,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 394 |
'from HBM; amortized '
|
| 395 |
'over 64 passes',
|
| 396 |
'units': 'GB/s',
|
| 397 |
-
'value':
|
| 398 |
-
'latency': {'clock_ghz': 2.
|
| 399 |
'confidence': 'high',
|
| 400 |
'method': 'Sattolo-cycle '
|
| 401 |
-
'pointer chase
|
| 402 |
-
'
|
| 403 |
-
'
|
| 404 |
-
'ring
|
|
|
|
| 405 |
'self-evicting '
|
| 406 |
'chain (2x L2 in '
|
| 407 |
'touched lines) for '
|
|
@@ -413,40 +425,58 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 413 |
'footprints',
|
| 414 |
'units': 'cycles (single-lane '
|
| 415 |
'dependent loads)',
|
| 416 |
-
'value': {'dram_cycles':
|
| 417 |
'l1_cycles': 45.8,
|
| 418 |
-
'l2_cycles':
|
| 419 |
'ns_per_cycle': 0.372,
|
| 420 |
'smem_cycles': 30.0}},
|
| 421 |
-
'launch_overhead': {'clock_ghz': 2.
|
| 422 |
-
'confidence': '
|
| 423 |
'method': 'empty-kernel '
|
| 424 |
-
'trains
|
| 425 |
-
'
|
| 426 |
-
'launch
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
'units': 'us',
|
| 432 |
-
'value': {'
|
| 433 |
-
'
|
| 434 |
-
|
|
|
|
|
|
|
| 435 |
'confidence': 'medium',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
'method': 'register-fragment '
|
| 437 |
'mma_sync loops (wmma; '
|
| 438 |
'fp8 and 2:4-sparse '
|
| 439 |
'fp16 via inline PTX '
|
| 440 |
-
'm16n8k32;
|
| 441 |
-
'
|
| 442 |
-
'
|
| 443 |
-
'
|
| 444 |
-
'
|
| 445 |
-
'
|
| 446 |
-
'
|
| 447 |
-
'
|
| 448 |
-
'
|
| 449 |
-
'
|
| 450 |
'note': 'dense-tile rate without '
|
| 451 |
'memory traffic; real '
|
| 452 |
'kernels see this only '
|
|
@@ -454,25 +484,54 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 454 |
'behind it',
|
| 455 |
'units': 'TFLOP/s (TOP/s for '
|
| 456 |
'int8)',
|
| 457 |
-
'value': {'bf16':
|
| 458 |
-
'fp16':
|
| 459 |
-
'fp16_sparse_2_4':
|
| 460 |
-
|
| 461 |
-
'
|
| 462 |
-
'
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
| 464 |
'vs_nominal': 1.0},
|
| 465 |
-
'fp16': {'
|
|
|
|
| 466 |
'vs_nominal': 1.0},
|
| 467 |
-
'fp16_sparse_2_4': {'
|
|
|
|
| 468 |
'vs_nominal': 1.0},
|
| 469 |
-
'fp8_e4m3': {'
|
| 470 |
-
'
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
'vs_nominal': 1.0},
|
| 473 |
-
'tf32': {'
|
|
|
|
| 474 |
'vs_nominal': 1.0}}},
|
| 475 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
'confidence': 'high',
|
| 477 |
'method': 'occupancy '
|
| 478 |
'API '
|
|
@@ -501,7 +560,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 501 |
'16KB': 3,
|
| 502 |
'32KB': 3,
|
| 503 |
'48KB': 2}}},
|
| 504 |
-
'occupancy_knees_fma_tflops': {'clock_ghz': 2.
|
| 505 |
'confidence': 'high',
|
| 506 |
'method': 'a '
|
| 507 |
'single '
|
|
@@ -521,17 +580,6 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 521 |
'fixed '
|
| 522 |
'total '
|
| 523 |
'work; '
|
| 524 |
-
'throughput '
|
| 525 |
-
'is '
|
| 526 |
-
'proportional '
|
| 527 |
-
'to '
|
| 528 |
-
'resident '
|
| 529 |
-
'threads '
|
| 530 |
-
'until '
|
| 531 |
-
'the '
|
| 532 |
-
'pipe '
|
| 533 |
-
'saturates, '
|
| 534 |
-
'so '
|
| 535 |
'the '
|
| 536 |
'cliff '
|
| 537 |
'marks '
|
|
@@ -549,15 +597,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 549 |
'above',
|
| 550 |
'units': 'TFLOP/s',
|
| 551 |
'value': {'t1024': {'0KB': 75.5,
|
| 552 |
-
'32KB': 73.
|
| 553 |
-
'48KB':
|
| 554 |
-
't128': {'0KB': 79.
|
| 555 |
'32KB': 60.31,
|
| 556 |
-
'48KB':
|
| 557 |
-
't256': {'0KB':
|
| 558 |
-
'32KB':
|
| 559 |
-
'48KB':
|
| 560 |
-
'smem_bw': {'clock_ghz': 2.
|
| 561 |
'confidence': 'medium',
|
| 562 |
'method': 'block-resident '
|
| 563 |
'read+write '
|
|
@@ -566,12 +614,56 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 566 |
'aggregated across '
|
| 567 |
'SMs',
|
| 568 |
'note': 'bank-conflict-light '
|
| 569 |
-
'access pattern;
|
| 570 |
-
'
|
| 571 |
-
'
|
|
|
|
| 572 |
'units': 'GB/s',
|
| 573 |
-
'value':
|
| 574 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
'confidence': 'high',
|
| 576 |
'method': '256 MB torch '
|
| 577 |
'copies, '
|
|
@@ -583,8 +675,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 583 |
'direction of '
|
| 584 |
'traffic',
|
| 585 |
'units': 'GB/s',
|
| 586 |
-
'value': {'d2d':
|
| 587 |
-
'd2h_pageable':
|
| 588 |
-
'd2h_pinned': 26.
|
| 589 |
-
'h2d_pageable':
|
| 590 |
'h2d_pinned': 24.0}}}}
|
|
|
|
| 1 |
+
'''Release-pinned device dossiers, one per certified architecture; generated by dev/pin_dossier.py from idle single-tenant runs, consumed by dossier_drift.'''
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 4 |
'confidence': 'high',
|
|
|
|
| 291 |
'd2h_pinned': 10.8,
|
| 292 |
'h2d_pageable': 10.3,
|
| 293 |
'h2d_pinned': 11.8}}},
|
| 294 |
+
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.705,
|
| 295 |
'confidence': 'high',
|
| 296 |
'method': 'atomicAdd '
|
| 297 |
'hammer; curve '
|
|
|
|
| 304 |
'contention) to '
|
| 305 |
'one per thread',
|
| 306 |
'units': 'Gop/s',
|
| 307 |
+
'value': {'curve_slots': {'1': 0.71,
|
| 308 |
+
'145408': 413.99,
|
| 309 |
+
'4096': 184.9,
|
| 310 |
+
'512': 49.84,
|
| 311 |
+
'64': 13.25,
|
| 312 |
+
'8': 5.74},
|
| 313 |
+
'shared_same_addr': 12.23}},
|
| 314 |
'clock': {'confidence': 'high',
|
| 315 |
+
'final_ghz': 2.698,
|
| 316 |
+
'max_ghz': 2.705,
|
| 317 |
'method': '24 back-to-back FMA '
|
| 318 |
'spin windows of 30M '
|
| 319 |
'ticks; wall/ticks '
|
| 320 |
'per window',
|
| 321 |
+
'min_ghz': 2.5,
|
| 322 |
'sag_pct': 0.3,
|
| 323 |
'units': 'GHz'},
|
| 324 |
'device': {'cuda_runtime': '12.6',
|
|
|
|
| 332 |
'sms': 142,
|
| 333 |
'torch': '2.10.0+cu126',
|
| 334 |
'vram_bytes': 48305274880},
|
| 335 |
+
'dram_bw': {'clock_ghz': 2.705,
|
| 336 |
'confidence': 'high',
|
| 337 |
'method': 'streaming triad '
|
| 338 |
'c=a+s*b per access '
|
|
|
|
| 352 |
'line-efficiency '
|
| 353 |
'claim',
|
| 354 |
'units': 'GB/s',
|
| 355 |
+
'value': {'random_gather_16B': 218.1,
|
| 356 |
+
'read_only': 766.5,
|
| 357 |
+
'triad_16B': 682.1,
|
| 358 |
+
'triad_4B': 654.8,
|
| 359 |
+
'triad_8B': 670.9,
|
| 360 |
'write_only': 766.5}},
|
| 361 |
+
'fma_f32': {'clock_ghz': 2.705,
|
| 362 |
'confidence': 'high',
|
| 363 |
'method': 'register-resident '
|
| 364 |
'FMA chains',
|
| 365 |
'units': 'TFLOP/s',
|
| 366 |
+
'value': 86.58},
|
| 367 |
+
'fma_f64': {'clock_ghz': 2.705,
|
| 368 |
'confidence': 'high',
|
| 369 |
'method': 'register-resident '
|
| 370 |
'double FMA chains '
|
| 371 |
'(the DFMA pipe)',
|
| 372 |
'units': 'TFLOP/s',
|
| 373 |
+
'value': 1.257},
|
| 374 |
+
'fma_ilp': {'clock_ghz': 2.705,
|
| 375 |
+
'confidence': 'high',
|
| 376 |
+
'method': '1/2/4 independent '
|
| 377 |
+
'dependent FMA '
|
| 378 |
+
'chains per thread '
|
| 379 |
+
'at one block of 64 '
|
| 380 |
+
'threads per SM; a '
|
| 381 |
+
'ratio near 4 means '
|
| 382 |
+
'latency-bound at '
|
| 383 |
+
'that occupancy',
|
| 384 |
+
'units': 'TFLOP/s',
|
| 385 |
+
'value': {'ilp1': 11.75,
|
| 386 |
+
'ilp2': 23.37,
|
| 387 |
+
'ilp4': 43.03,
|
| 388 |
+
'ratio_4_1': 3.66}},
|
| 389 |
+
'grid_barrier': {'clock_ghz': 2.705,
|
| 390 |
'confidence': 'high',
|
| 391 |
'method': 'cooperative '
|
| 392 |
'kernel, 142 '
|
|
|
|
| 394 |
'threads',
|
| 395 |
'units': 'us per '
|
| 396 |
'grid.sync',
|
| 397 |
+
'value': 0.55},
|
| 398 |
+
'l2_bw': {'clock_ghz': 2.705,
|
| 399 |
'confidence': 'medium',
|
| 400 |
'method': 'in-kernel repeated '
|
| 401 |
'triad passes over 72 '
|
|
|
|
| 405 |
'from HBM; amortized '
|
| 406 |
'over 64 passes',
|
| 407 |
'units': 'GB/s',
|
| 408 |
+
'value': 9590.6},
|
| 409 |
+
'latency': {'clock_ghz': 2.691,
|
| 410 |
'confidence': 'high',
|
| 411 |
'method': 'Sattolo-cycle '
|
| 412 |
+
'pointer chase, min '
|
| 413 |
+
'of 3 runs; 16 KB '
|
| 414 |
+
'ring for L1, 2 MB '
|
| 415 |
+
'TLB-resident ring '
|
| 416 |
+
'for L2, '
|
| 417 |
'self-evicting '
|
| 418 |
'chain (2x L2 in '
|
| 419 |
'touched lines) for '
|
|
|
|
| 425 |
'footprints',
|
| 426 |
'units': 'cycles (single-lane '
|
| 427 |
'dependent loads)',
|
| 428 |
+
'value': {'dram_cycles': 656.9,
|
| 429 |
'l1_cycles': 45.8,
|
| 430 |
+
'l2_cycles': 292.0,
|
| 431 |
'ns_per_cycle': 0.372,
|
| 432 |
'smem_cycles': 30.0}},
|
| 433 |
+
'launch_overhead': {'clock_ghz': 2.705,
|
| 434 |
+
'confidence': 'medium',
|
| 435 |
'method': 'empty-kernel '
|
| 436 |
+
'trains; '
|
| 437 |
+
'torch path '
|
| 438 |
+
'per-launch '
|
| 439 |
+
'through op '
|
| 440 |
+
'dispatch, '
|
| 441 |
+
'raw path '
|
| 442 |
+
'looped '
|
| 443 |
+
'inside one '
|
| 444 |
+
'binding '
|
| 445 |
+
'call',
|
| 446 |
+
'note': 'host-state-dependent; '
|
| 447 |
+
'the '
|
| 448 |
+
'torch-minus-raw '
|
| 449 |
+
'gap is the '
|
| 450 |
+
'dispatch '
|
| 451 |
+
'cost a '
|
| 452 |
+
'torch-launched '
|
| 453 |
+
'candidate '
|
| 454 |
+
'pays',
|
| 455 |
'units': 'us',
|
| 456 |
+
'value': {'raw_queued_us': 6.75,
|
| 457 |
+
'raw_round_trip_us': 13.87,
|
| 458 |
+
'torch_queued_us': 7.09,
|
| 459 |
+
'torch_round_trip_us': 18.53}},
|
| 460 |
+
'mma': {'clock_ghz': 2.705,
|
| 461 |
'confidence': 'medium',
|
| 462 |
+
'fp8_accum': {'fp8_e4m3': {'effective_accum_mantissa_bits': 8,
|
| 463 |
+
'stall_element_value': 16384.0},
|
| 464 |
+
'fp8_e5m2': {'effective_accum_mantissa_bits': 8,
|
| 465 |
+
'stall_element_value': 16384.0}},
|
| 466 |
'method': 'register-fragment '
|
| 467 |
'mma_sync loops (wmma; '
|
| 468 |
'fp8 and 2:4-sparse '
|
| 469 |
'fp16 via inline PTX '
|
| 470 |
+
'm16n8k32); flops '
|
| 471 |
+
'derive from a '
|
| 472 |
+
'sub-saturation '
|
| 473 |
+
'accumulator read-back '
|
| 474 |
+
'with every fragment '
|
| 475 |
+
'element observed, '
|
| 476 |
+
'never the declared '
|
| 477 |
+
'loop shape; sparse '
|
| 478 |
+
'reports executed and '
|
| 479 |
+
'dense-equivalent flops',
|
| 480 |
'note': 'dense-tile rate without '
|
| 481 |
'memory traffic; real '
|
| 482 |
'kernels see this only '
|
|
|
|
| 484 |
'behind it',
|
| 485 |
'units': 'TFLOP/s (TOP/s for '
|
| 486 |
'int8)',
|
| 487 |
+
'value': {'bf16': 369.6,
|
| 488 |
+
'fp16': 365.3,
|
| 489 |
+
'fp16_sparse_2_4': {'dense_equivalent': 711.1,
|
| 490 |
+
'executed': 355.6},
|
| 491 |
+
'fp8_e4m3': 772.3,
|
| 492 |
+
'fp8_e5m2': 771.2,
|
| 493 |
+
'int8': 750.0,
|
| 494 |
+
'tf32': 178.3},
|
| 495 |
+
'verified_k_lanes': {'bf16': {'calibration_linear': True,
|
| 496 |
+
'executed_k_per_iter': 64.0,
|
| 497 |
'vs_nominal': 1.0},
|
| 498 |
+
'fp16': {'calibration_linear': True,
|
| 499 |
+
'executed_k_per_iter': 64.0,
|
| 500 |
'vs_nominal': 1.0},
|
| 501 |
+
'fp16_sparse_2_4': {'calibration_linear': True,
|
| 502 |
+
'executed_k_per_iter': 128.0,
|
| 503 |
'vs_nominal': 1.0},
|
| 504 |
+
'fp8_e4m3': {'calibration_linear': True,
|
| 505 |
+
'executed_k_per_iter': 256.0,
|
| 506 |
+
'vs_nominal': 1.0},
|
| 507 |
+
'fp8_e5m2': {'calibration_linear': True,
|
| 508 |
+
'executed_k_per_iter': 256.0,
|
| 509 |
+
'vs_nominal': 1.0},
|
| 510 |
+
'int8': {'calibration_linear': True,
|
| 511 |
+
'executed_k_per_iter': 64.0,
|
| 512 |
'vs_nominal': 1.0},
|
| 513 |
+
'tf32': {'calibration_linear': True,
|
| 514 |
+
'executed_k_per_iter': 32.0,
|
| 515 |
'vs_nominal': 1.0}}},
|
| 516 |
+
'mma_fed': {'clock_ghz': 2.705,
|
| 517 |
+
'confidence': 'high',
|
| 518 |
+
'method': 'm16n16k16 fp16 '
|
| 519 |
+
'tiles fed from an '
|
| 520 |
+
'L2-resident buffer '
|
| 521 |
+
'through cp.async '
|
| 522 |
+
'double-buffered '
|
| 523 |
+
'shared memory; '
|
| 524 |
+
'accumulator-verified',
|
| 525 |
+
'note': 'the achieved '
|
| 526 |
+
'counterpart to the '
|
| 527 |
+
'register peak: '
|
| 528 |
+
'staging exposed, one '
|
| 529 |
+
'dependent '
|
| 530 |
+
'accumulator chain '
|
| 531 |
+
'per warp',
|
| 532 |
+
'units': 'TFLOP/s',
|
| 533 |
+
'value': 85.2},
|
| 534 |
+
'occupancy_blocks_per_sm': {'clock_ghz': 2.705,
|
| 535 |
'confidence': 'high',
|
| 536 |
'method': 'occupancy '
|
| 537 |
'API '
|
|
|
|
| 560 |
'16KB': 3,
|
| 561 |
'32KB': 3,
|
| 562 |
'48KB': 2}}},
|
| 563 |
+
'occupancy_knees_fma_tflops': {'clock_ghz': 2.705,
|
| 564 |
'confidence': 'high',
|
| 565 |
'method': 'a '
|
| 566 |
'single '
|
|
|
|
| 580 |
'fixed '
|
| 581 |
'total '
|
| 582 |
'work; '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
'the '
|
| 584 |
'cliff '
|
| 585 |
'marks '
|
|
|
|
| 597 |
'above',
|
| 598 |
'units': 'TFLOP/s',
|
| 599 |
'value': {'t1024': {'0KB': 75.5,
|
| 600 |
+
'32KB': 73.71,
|
| 601 |
+
'48KB': 72.69},
|
| 602 |
+
't128': {'0KB': 79.72,
|
| 603 |
'32KB': 60.31,
|
| 604 |
+
'48KB': 45.81},
|
| 605 |
+
't256': {'0KB': 78.79,
|
| 606 |
+
'32KB': 78.85,
|
| 607 |
+
'48KB': 72.97}}},
|
| 608 |
+
'smem_bw': {'clock_ghz': 2.705,
|
| 609 |
'confidence': 'medium',
|
| 610 |
'method': 'block-resident '
|
| 611 |
'read+write '
|
|
|
|
| 614 |
'aggregated across '
|
| 615 |
'SMs',
|
| 616 |
'note': 'bank-conflict-light '
|
| 617 |
+
'access pattern; the '
|
| 618 |
+
'stride sweep below '
|
| 619 |
+
'prices the '
|
| 620 |
+
'conflicted case',
|
| 621 |
'units': 'GB/s',
|
| 622 |
+
'value': 22244.0},
|
| 623 |
+
'smem_conflict_penalty': {'clock_ghz': 2.705,
|
| 624 |
+
'confidence': 'high',
|
| 625 |
+
'method': 'dependent '
|
| 626 |
+
'read-FMA-write '
|
| 627 |
+
'per '
|
| 628 |
+
'thread '
|
| 629 |
+
'at '
|
| 630 |
+
'warp-lane '
|
| 631 |
+
'bank '
|
| 632 |
+
'distance '
|
| 633 |
+
'`stride`; '
|
| 634 |
+
'stride '
|
| 635 |
+
'32 '
|
| 636 |
+
'puts '
|
| 637 |
+
'every '
|
| 638 |
+
'lane '
|
| 639 |
+
'in '
|
| 640 |
+
'one '
|
| 641 |
+
'bank',
|
| 642 |
+
'note': 'the '
|
| 643 |
+
'direct '
|
| 644 |
+
'conflict '
|
| 645 |
+
'measurement '
|
| 646 |
+
'that '
|
| 647 |
+
'disambiguates '
|
| 648 |
+
'a '
|
| 649 |
+
'bank-conflict '
|
| 650 |
+
'signature '
|
| 651 |
+
'from '
|
| 652 |
+
'an ILP '
|
| 653 |
+
'stall',
|
| 654 |
+
'units': 'x '
|
| 655 |
+
'(time '
|
| 656 |
+
'ratio '
|
| 657 |
+
'vs '
|
| 658 |
+
'stride '
|
| 659 |
+
'1)',
|
| 660 |
+
'value': {'stride_1': 1.0,
|
| 661 |
+
'stride_16': 7.13,
|
| 662 |
+
'stride_2': 1.07,
|
| 663 |
+
'stride_32': 14.08,
|
| 664 |
+
'stride_4': 2.04,
|
| 665 |
+
'stride_8': 3.91}},
|
| 666 |
+
'transfer_bw': {'clock_ghz': 2.705,
|
| 667 |
'confidence': 'high',
|
| 668 |
'method': '256 MB torch '
|
| 669 |
'copies, '
|
|
|
|
| 675 |
'direction of '
|
| 676 |
'traffic',
|
| 677 |
'units': 'GB/s',
|
| 678 |
+
'value': {'d2d': 324.0,
|
| 679 |
+
'd2h_pageable': 7.8,
|
| 680 |
+
'd2h_pinned': 26.3,
|
| 681 |
+
'h2d_pageable': 7.6,
|
| 682 |
'h2d_pinned': 24.0}}}}
|
build/torch212-cxx11-cu126-x86_64-linux/metadata.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 7 |
"backend": {
|
| 8 |
"type": "cuda",
|
| 9 |
"archs": [
|
|
@@ -16,21 +17,20 @@
|
|
| 16 |
"digest": {
|
| 17 |
"algorithm": "sha256",
|
| 18 |
"files": {
|
| 19 |
-
"__init__.py": "
|
| 20 |
-
"
|
| 21 |
-
"_ops.py": "
|
| 22 |
-
"_pinned.py": "
|
| 23 |
-
"metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 24 |
}
|
| 25 |
},
|
| 26 |
"provenance": {
|
| 27 |
"kernel-builder": {
|
| 28 |
"version": "0.17.0-dev0",
|
| 29 |
-
"sha": "
|
| 30 |
"dirty": false
|
| 31 |
},
|
| 32 |
"kernel": {
|
| 33 |
-
"sha": "
|
| 34 |
"dirty": false
|
| 35 |
}
|
| 36 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
+
"id": "_metakernel_cuda_0b9de42",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
| 7 |
+
"kernel-depends": [],
|
| 8 |
"backend": {
|
| 9 |
"type": "cuda",
|
| 10 |
"archs": [
|
|
|
|
| 17 |
"digest": {
|
| 18 |
"algorithm": "sha256",
|
| 19 |
"files": {
|
| 20 |
+
"__init__.py": "lDQ+fJ0JnI3sQGR3UMnDovBbG4ciYBsaFjmgYdKDTKI=",
|
| 21 |
+
"_metakernel_cuda_0b9de42.abi3.so": "sUOxA7K/jfqRKcYtfvezMVV0nuoG9/Dz7gCajl53NsU=",
|
| 22 |
+
"_ops.py": "x88Bkm1nxRli0nIu2u4ztaJopS1IAZWdN9LZPfspJdo=",
|
| 23 |
+
"_pinned.py": "UyzMniAjfVMIrIbbrSq92KKo5dX5l/GwV3pMVA+eNtM="
|
|
|
|
| 24 |
}
|
| 25 |
},
|
| 26 |
"provenance": {
|
| 27 |
"kernel-builder": {
|
| 28 |
"version": "0.17.0-dev0",
|
| 29 |
+
"sha": "2c40e10d3da1392245ee81bd05b55fb3103b2b34",
|
| 30 |
"dirty": false
|
| 31 |
},
|
| 32 |
"kernel": {
|
| 33 |
+
"sha": "0b9de42fee8a1e52593f2f1f58460c0acc93d7e8",
|
| 34 |
"dirty": false
|
| 35 |
}
|
| 36 |
}
|
build/torch212-cxx11-cu126-x86_64-linux/metakernel/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 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
CHANGED
|
@@ -1,20 +1,4 @@
|
|
| 1 |
-
"""Measuring instrument for the kernel-design loop: timing, adversarial
|
| 2 |
-
correctness comparison, and measured device facts, as loadable ops.
|
| 3 |
-
|
| 4 |
-
from kernels import get_kernel
|
| 5 |
-
mk = get_kernel("phanerozoic/metakernel", version=1,
|
| 6 |
-
trust_remote_code=True)
|
| 7 |
-
|
| 8 |
-
dossier = mk.probe_device() # measured facts
|
| 9 |
-
report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
|
| 10 |
-
verdict = mk.compare(ref, cand, gen) # ULP evidence
|
| 11 |
-
table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
|
| 12 |
-
|
| 13 |
-
Every op returns plain dicts and tensors. Every dossier entry is
|
| 14 |
-
measured by a dedicated probe kernel, carries its SM clock, and labels
|
| 15 |
-
throttled samples; no hardware counters, so entries carry a confidence
|
| 16 |
-
field.
|
| 17 |
-
"""
|
| 18 |
import math
|
| 19 |
import os
|
| 20 |
import time
|
|
@@ -24,9 +8,7 @@ import torch
|
|
| 24 |
try:
|
| 25 |
from ._ops import ops
|
| 26 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 27 |
-
#
|
| 28 |
-
# checkout there is no generated _ops module, so rebuild the op table
|
| 29 |
-
# from the (cached) JIT extension that load_local advertised.
|
| 30 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 31 |
if not _root:
|
| 32 |
raise
|
|
@@ -48,24 +30,25 @@ try:
|
|
| 48 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 49 |
_PINNED = {}
|
| 50 |
|
| 51 |
-
__all__ = ["probe_device", "bench", "compare", "
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"pinned_dossiers", "
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
#
|
| 62 |
_MMA_KINDS = {
|
| 63 |
-
"fp16": (0, 16 * 16, 16,
|
| 64 |
-
"bf16": (1, 16 * 16, 16,
|
| 65 |
-
"tf32": (2, 16 * 16, 8,
|
| 66 |
-
"int8": (3, 16 * 16, 16,
|
| 67 |
-
"fp8_e4m3": (4, 16 * 8, 32,
|
| 68 |
-
"fp16_sparse_2_4": (5, 16 * 8, 32,
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
|
|
@@ -74,7 +57,7 @@ def _sync():
|
|
| 74 |
|
| 75 |
|
| 76 |
def _events(fn, reps):
|
| 77 |
-
"""Time reps calls of fn with one
|
| 78 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 79 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 80 |
for s, e in zip(starts, stops):
|
|
@@ -86,8 +69,7 @@ def _events(fn, reps):
|
|
| 86 |
|
| 87 |
|
| 88 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 89 |
-
"""Effective SM clock in GHz
|
| 90 |
-
Wall time over elapsed ticks is the frequency the ticks ran at."""
|
| 91 |
props = torch.cuda.get_device_properties(device)
|
| 92 |
if blocks is None:
|
| 93 |
blocks = props.multi_processor_count
|
|
@@ -120,8 +102,7 @@ def _entry(value, units, method, clock_ghz, confidence, note=""):
|
|
| 120 |
|
| 121 |
|
| 122 |
def _sattolo_ring(n, device, generator=None):
|
| 123 |
-
"""
|
| 124 |
-
a pointer chase visits every slot before repeating."""
|
| 125 |
perm = torch.randperm(n, generator=generator)
|
| 126 |
ring = torch.empty(n, dtype=torch.int32)
|
| 127 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
@@ -129,8 +110,7 @@ def _sattolo_ring(n, device, generator=None):
|
|
| 129 |
|
| 130 |
|
| 131 |
def _smi_settings(index):
|
| 132 |
-
"""ECC mode, driver,
|
| 133 |
-
when the tool is unavailable."""
|
| 134 |
import subprocess
|
| 135 |
try:
|
| 136 |
out = subprocess.run(
|
|
@@ -150,10 +130,19 @@ def _smi_settings(index):
|
|
| 150 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 151 |
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def mma_tile_check(device="cuda", trials=8):
|
| 154 |
-
"""Random small-integer m16n16k16 tiles through the wmma
|
| 155 |
-
path, compared exactly to the reference GEMM; pins M, N, K, and
|
| 156 |
-
operand mapping (integer operands make fp32 accumulation exact)."""
|
| 157 |
worst = 0.0
|
| 158 |
for t in range(trials):
|
| 159 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
@@ -171,10 +160,83 @@ def mma_tile_check(device="cuda", trials=8):
|
|
| 171 |
"trials": trials}
|
| 172 |
|
| 173 |
|
| 174 |
-
def
|
| 175 |
-
"""
|
| 176 |
-
|
| 177 |
-
a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
props = torch.cuda.get_device_properties(device)
|
| 179 |
sm = props.multi_processor_count
|
| 180 |
l2 = props.L2_cache_size
|
|
@@ -187,8 +249,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 187 |
"vram_bytes": props.total_memory,
|
| 188 |
"torch": torch.__version__,
|
| 189 |
"cuda_runtime": torch.version.cuda,
|
| 190 |
-
# instrument settings
|
| 191 |
-
# dossier records them (None when nvidia-smi is unavailable)
|
| 192 |
**_smi_settings(torch.device(device).index or 0),
|
| 193 |
}
|
| 194 |
}
|
|
@@ -213,9 +274,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 213 |
}
|
| 214 |
clk = max(traj)
|
| 215 |
|
| 216 |
-
# ---- device-memory bandwidth
|
| 217 |
-
|
| 218 |
-
n4 = 16 << 20 # 16M float4 = 256 MB per buffer
|
| 219 |
if props.total_memory < 4 << 30:
|
| 220 |
n4 = 4 << 20
|
| 221 |
a = torch.rand(n4 * 4, device=device)
|
|
@@ -239,7 +299,6 @@ def probe_device(device="cuda", quick=False):
|
|
| 239 |
dtype=torch.int32).to(device)
|
| 240 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 241 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
| 242 |
-
# traffic accounting: idx reads + 16B random loads + streamed stores
|
| 243 |
hbm["random_gather_16B"] = round(
|
| 244 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 245 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
@@ -250,9 +309,9 @@ def probe_device(device="cuda", quick=False):
|
|
| 250 |
"so its nominal number is the achievable rate for "
|
| 251 |
"random access, not a line-efficiency claim")
|
| 252 |
|
| 253 |
-
# ---- L2 bandwidth
|
| 254 |
ws = max(l2 // 4, 1 << 20)
|
| 255 |
-
nl2 = ws // 16
|
| 256 |
al = a[:nl2 * 4].contiguous()
|
| 257 |
bl = b[:nl2 * 4].contiguous()
|
| 258 |
cl = c[:nl2 * 4].contiguous()
|
|
@@ -267,62 +326,61 @@ def probe_device(device="cuda", quick=False):
|
|
| 267 |
"medium", "first pass streams from HBM; amortized "
|
| 268 |
f"over {passes} passes")
|
| 269 |
|
| 270 |
-
# ---- latencies:
|
| 271 |
-
# L1, fits L2 everywhere, and stays TLB-resident after a warm walk;
|
| 272 |
-
# the DRAM chain touches 2x L2 in lines, so it evicts itself.
|
| 273 |
lat = {}
|
| 274 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
| 275 |
-
ring_s = _sattolo_ring(8192, device)
|
| 276 |
-
ops.mk_chase_shared(ring_s, 1 << 14, out64)
|
| 277 |
-
_sync()
|
| 278 |
-
hops = 1 << 16
|
| 279 |
-
ops.mk_chase_shared(ring_s, hops, out64)
|
| 280 |
-
_sync()
|
| 281 |
-
lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
_sync()
|
| 299 |
-
lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 302 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 303 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
_sync()
|
| 308 |
-
lat["dram_cycles"] = round(int(out64[0].item()) / hops_h, 1)
|
| 309 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 310 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 311 |
-
"Sattolo-cycle pointer chase
|
| 312 |
-
"2 MB TLB-resident ring for L2,
|
| 313 |
-
"chain (2x L2 in touched lines)
|
| 314 |
-
idle_clk, "high",
|
| 315 |
"smem includes index arithmetic; HBM includes "
|
| 316 |
"residual TLB effects at large footprints")
|
| 317 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 318 |
|
| 319 |
-
# ---- fp32 FMA and tensor-core throughput
|
| 320 |
blocks = sm * 4
|
| 321 |
iters = 20_000 if quick else 60_000
|
| 322 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 323 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 324 |
reps))
|
| 325 |
-
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 326 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 327 |
"register-resident FMA chains", clk, "high")
|
| 328 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
@@ -334,43 +392,88 @@ def probe_device(device="cuda", quick=False):
|
|
| 334 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 335 |
"register-resident double FMA chains (the DFMA "
|
| 336 |
"pipe)", clk, "high")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
mma = {}
|
| 338 |
mma_klanes = {}
|
| 339 |
-
|
|
|
|
| 340 |
it = 30_000 if quick else 100_000
|
| 341 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 342 |
if r != 0:
|
| 343 |
mma[name] = "unsupported"
|
| 344 |
continue
|
| 345 |
_sync()
|
| 346 |
-
|
| 347 |
-
# the executed K-lane count (exact at these magnitudes)
|
| 348 |
-
cal_iters = 512
|
| 349 |
-
sink[1] = 0.0
|
| 350 |
-
ops.mk_mma(kind, 1, 1, cal_iters, sink)
|
| 351 |
-
_sync()
|
| 352 |
-
gain_per_iter = float(sink[1].item()) / cal_iters
|
| 353 |
mma_klanes[name] = {
|
| 354 |
-
"executed_k_per_iter": round(
|
| 355 |
-
"vs_nominal": round(
|
|
|
|
| 356 |
}
|
|
|
|
|
|
|
| 357 |
ms = _median(_events(
|
| 358 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 362 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 363 |
-
"2:4-sparse fp16 via inline PTX m16n8k32;
|
| 364 |
-
"
|
| 365 |
-
"flops derive from the mean accumulator read-back "
|
| 366 |
"with every fragment element observed, never the "
|
| 367 |
-
"declared loop shape"
|
|
|
|
| 368 |
clk, "medium",
|
| 369 |
"dense-tile rate without memory traffic; real "
|
| 370 |
"kernels see this only when staging hides behind it")
|
| 371 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
# ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
it_sm = 30_000 if quick else 100_000
|
| 375 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 376 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
@@ -380,8 +483,23 @@ def probe_device(device="cuda", quick=False):
|
|
| 380 |
"block-resident read+write streaming (8 B per "
|
| 381 |
"thread-iteration), aggregated across SMs", clk,
|
| 382 |
"medium",
|
| 383 |
-
"bank-conflict-light access pattern;
|
| 384 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
# ---- PCIe / host transfer bandwidth
|
| 387 |
nbytes = 256 << 20
|
|
@@ -412,9 +530,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 412 |
"d2d counts one direction of traffic")
|
| 413 |
del host_pin, host_pag, dev_t, dev_u
|
| 414 |
|
| 415 |
-
# ---- atomic contention curve
|
| 416 |
-
# addresses the whole grid hammers (slots = 1 is full contention;
|
| 417 |
-
# slots = threads is contention-free), plus the shared-memory point
|
| 418 |
n_thr = blocks * 256
|
| 419 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 420 |
at = {"curve_slots": {}}
|
|
@@ -424,7 +540,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 424 |
buf.zero_()
|
| 425 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 426 |
ms = _median(_events(
|
| 427 |
-
lambda
|
| 428 |
reps))
|
| 429 |
at["curve_slots"][str(slots)] = round(
|
| 430 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
@@ -437,7 +553,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 437 |
"of distinct global addresses from 1 (full "
|
| 438 |
"contention) to one per thread", clk, "high")
|
| 439 |
|
| 440 |
-
# ---- launch overhead
|
| 441 |
anchor = sink
|
| 442 |
n_launch = 500 if quick else 2000
|
| 443 |
_sync()
|
|
@@ -452,10 +568,19 @@ def probe_device(device="cuda", quick=False):
|
|
| 452 |
ops.mk_empty(anchor)
|
| 453 |
_sync()
|
| 454 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
|
|
|
|
|
|
| 455 |
d["launch_overhead"] = _entry(
|
| 456 |
-
{"
|
| 457 |
-
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
# ---- grid barrier
|
| 461 |
bar_iters = 500 if quick else 2000
|
|
@@ -471,9 +596,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 471 |
"unavailable or grid too large", clk,
|
| 472 |
"high")
|
| 473 |
|
| 474 |
-
# ---- occupancy
|
| 475 |
-
# at every (threads, dyn_smem) point; when the forced blocks/SM stop
|
| 476 |
-
# covering memory latency, achieved bandwidth falls off the cliff
|
| 477 |
occ = {}
|
| 478 |
for threads in (128, 256, 512, 1024):
|
| 479 |
row = {}
|
|
@@ -494,8 +617,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 494 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 495 |
knee_iters, sink)
|
| 496 |
ms = _median(_events(
|
| 497 |
-
lambda t=threads,
|
| 498 |
-
knee_blocks, t,
|
| 499 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 500 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 501 |
knees[f"t{threads}"] = row
|
|
@@ -504,27 +627,160 @@ def probe_device(device="cuda", quick=False):
|
|
| 504 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 505 |
"the cliff marks where occupancy stops covering latency",
|
| 506 |
clk, "high", "timing-derived counterpart to the API table above")
|
|
|
|
|
|
|
|
|
|
| 507 |
return d
|
| 508 |
|
| 509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 511 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 512 |
-
device="cuda"):
|
| 513 |
-
"""Median-of-N cudaEvent timing with throttle rejection.
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
around every sample, ~0.3 ms each."""
|
| 523 |
for _ in range(warmup):
|
| 524 |
fn()
|
| 525 |
_sync()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
graph_state = "off"
|
| 527 |
graph_reason = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
if graph:
|
| 529 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 530 |
try:
|
|
@@ -539,10 +795,7 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 539 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 540 |
graph_state = "fallback_eager"
|
| 541 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 542 |
-
# drain the invalidated stream
|
| 543 |
-
# On some torch versions a failed capture also wedges the
|
| 544 |
-
# device RNG with no in-process recovery; timing uses no RNG,
|
| 545 |
-
# but the caller's later randn may raise.
|
| 546 |
rng_ok = True
|
| 547 |
for _attempt in range(5):
|
| 548 |
try:
|
|
@@ -572,25 +825,51 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 572 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 573 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 574 |
best_clk = max(clocks)
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
flat = []
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
kept.append(ms)
|
| 585 |
else:
|
| 586 |
rejected += 1
|
| 587 |
-
|
|
|
|
| 588 |
kept = flat
|
| 589 |
med = _median(kept)
|
| 590 |
ks = sorted(kept)
|
| 591 |
n_k = len(ks)
|
| 592 |
-
# order-statistic 95% CI on the median
|
| 593 |
-
# binomial ranks); degenerate at tiny n, where it spans the sample
|
| 594 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 595 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 596 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
@@ -605,12 +884,25 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 605 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 606 |
"samples": len(flat),
|
| 607 |
"rejected_throttled": rejected,
|
|
|
|
| 608 |
"graph_captured": graph_state == "captured",
|
| 609 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 610 |
"worst": round(min(clocks), 3)},
|
| 611 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
if graph_reason:
|
| 613 |
rep["graph_fallback_reason"] = graph_reason
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
if bytes is not None:
|
| 615 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 616 |
if flops is not None:
|
|
@@ -620,6 +912,8 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 620 |
if dossier is not None and bytes and flops:
|
| 621 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 622 |
mma_v = dossier["mma"]["value"].get(dtype)
|
|
|
|
|
|
|
| 623 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 624 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 625 |
ai = flops / bytes
|
|
@@ -630,13 +924,12 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 630 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 631 |
"peak_dtype": dtype,
|
| 632 |
}
|
|
|
|
| 633 |
return rep
|
| 634 |
|
| 635 |
|
| 636 |
def _to_ordered_int(t):
|
| 637 |
-
"""
|
| 638 |
-
distance: positive floats keep their bits, negative floats mirror
|
| 639 |
-
below zero, so the mapping is monotone in the float value."""
|
| 640 |
if t.dtype == torch.float32:
|
| 641 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 642 |
half = 1 << 31
|
|
@@ -652,11 +945,12 @@ def _to_ordered_int(t):
|
|
| 652 |
|
| 653 |
|
| 654 |
def ulp_diff(a, b):
|
| 655 |
-
"""Elementwise ULP distance
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
|
|
|
| 660 |
if not a.is_floating_point():
|
| 661 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 662 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
@@ -673,9 +967,7 @@ class order_shuffles:
|
|
| 673 |
|
| 674 |
@staticmethod
|
| 675 |
def last_dim():
|
| 676 |
-
"""
|
| 677 |
-
accumulation order of a last-dim reduction changes, the value
|
| 678 |
-
does not."""
|
| 679 |
def shuffle(args, seed):
|
| 680 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 681 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
@@ -685,8 +977,7 @@ class order_shuffles:
|
|
| 685 |
|
| 686 |
@staticmethod
|
| 687 |
def matmul_k():
|
| 688 |
-
"""
|
| 689 |
-
product is unchanged, the accumulation order is not."""
|
| 690 |
def shuffle(args, seed):
|
| 691 |
a_m, b_m = args[0], args[1]
|
| 692 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
@@ -700,37 +991,70 @@ def _as_outputs(x):
|
|
| 700 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 701 |
|
| 702 |
|
| 703 |
-
def
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
worst = {"max_ulp": -1}
|
| 722 |
total_hist = {}
|
| 723 |
per_trial = []
|
| 724 |
max_abs_seen = 0.0
|
| 725 |
max_rel_seen = 0.0
|
|
|
|
| 726 |
for t in range(trials):
|
| 727 |
args = gen(seed + t)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
r_outs = _as_outputs(ref(*args))
|
| 729 |
c_outs = _as_outputs(cand(*args))
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
bands = None
|
| 735 |
band = 0
|
| 736 |
band_score = 0.0
|
|
@@ -754,8 +1078,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 754 |
max_ulp = 0
|
| 755 |
mean_ulp = 0.0
|
| 756 |
first_div = -1
|
|
|
|
| 757 |
n_el = 0
|
| 758 |
n_above_band = 0
|
|
|
|
|
|
|
| 759 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 760 |
worst_elements = []
|
| 761 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
@@ -773,8 +1100,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 773 |
mean_ulp += float(d.double().sum().item())
|
| 774 |
n_el += d.numel()
|
| 775 |
nz = (d > 0)
|
|
|
|
|
|
|
| 776 |
if first_div < 0 and nz.any():
|
| 777 |
first_div = int(nz.flatten().nonzero()[0].item())
|
|
|
|
| 778 |
if nz.any():
|
| 779 |
buckets = torch.clamp(
|
| 780 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
@@ -791,6 +1121,7 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 791 |
if val > 0:
|
| 792 |
worst_elements.append({
|
| 793 |
"output": oi, "index": int(ix),
|
|
|
|
| 794 |
"ref": float(rf[ix].item()),
|
| 795 |
"cand": float(cf[ix].item()),
|
| 796 |
"ulp": int(val)})
|
|
@@ -810,23 +1141,27 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 810 |
inf_r += int(torch.isinf(r).sum().item())
|
| 811 |
inf_c += int(torch.isinf(c).sum().item())
|
| 812 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
|
|
|
| 813 |
trial_rep = {
|
| 814 |
"max_ulp": max_ulp,
|
| 815 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 816 |
"first_divergent_index": first_div,
|
|
|
|
| 817 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 818 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 819 |
"order_band_ulp": band,
|
| 820 |
"band_score": round(band_score, 2),
|
| 821 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
|
|
|
|
|
|
| 822 |
"worst_elements": worst_elements[:worst_k],
|
| 823 |
}
|
| 824 |
per_trial.append(trial_rep)
|
| 825 |
if max_ulp > worst["max_ulp"]:
|
| 826 |
worst = trial_rep
|
| 827 |
-
band_max = max(t["order_band_ulp"] for t in per_trial)
|
| 828 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 829 |
-
|
|
|
|
| 830 |
score = max(t["band_score"] for t in per_trial)
|
| 831 |
ulp_ok = score <= band_margin
|
| 832 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
@@ -841,8 +1176,34 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 841 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 842 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 843 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 844 |
-
|
| 845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
"criterion": criterion,
|
| 847 |
"worst_trial": worst,
|
| 848 |
"max_abs": max_abs_seen,
|
|
@@ -850,6 +1211,9 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 850 |
"trials": per_trial,
|
| 851 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 852 |
}
|
|
|
|
|
|
|
|
|
|
| 853 |
|
| 854 |
|
| 855 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
@@ -865,6 +1229,30 @@ def _context_alive(device):
|
|
| 865 |
return False
|
| 866 |
|
| 867 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
def _fuzz_build(make, shape, dtype):
|
| 869 |
try:
|
| 870 |
out = make(tuple(shape), dtype)
|
|
@@ -881,7 +1269,7 @@ def _fuzz_variant(x, v):
|
|
| 881 |
if v == "strided_rows":
|
| 882 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 883 |
if v == "offset_slice":
|
| 884 |
-
#
|
| 885 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 886 |
if v == "overflow_expand":
|
| 887 |
if x.numel() == 0:
|
|
@@ -895,8 +1283,7 @@ def _fuzz_variant(x, v):
|
|
| 895 |
|
| 896 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 897 |
stop_on_context_loss=True):
|
| 898 |
-
"""All variant cases for one (shape, dtype) group; returns (rows,
|
| 899 |
-
context_lost)."""
|
| 900 |
rows = []
|
| 901 |
bases = _fuzz_build(make, shape, dtype)
|
| 902 |
lost = False
|
|
@@ -933,8 +1320,7 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 933 |
got = op(*xs)
|
| 934 |
want = ref(*xs)
|
| 935 |
if v == "overflow_expand":
|
| 936 |
-
# bounded comparison:
|
| 937 |
-
# which is where int32 flat indexing wraps
|
| 938 |
w = 1 << 20
|
| 939 |
gf, wf = got.flatten(), want.flatten()
|
| 940 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
@@ -959,20 +1345,30 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 959 |
return rows, lost
|
| 960 |
|
| 961 |
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
import
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 974 |
meta = json.load(open(sys.argv[1], "r"))
|
| 975 |
-
sys.path[:0] = meta["sys_path"]
|
| 976 |
try:
|
| 977 |
import kernels
|
| 978 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
@@ -985,85 +1381,165 @@ except Exception:
|
|
| 985 |
import load_local
|
| 986 |
_mk = load_local.load()
|
| 987 |
import torch
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 994 |
"""
|
| 995 |
|
| 996 |
|
| 997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 999 |
-
isolate_timeout_s=180, device="cuda"):
|
| 1000 |
-
"""
|
| 1001 |
-
report ok / mismatch / exception per case.
|
| 1002 |
-
|
| 1003 |
-
`make(shape, dtype)` (or `make(shape)`) builds a tensor or tuple;
|
| 1004 |
-
layout variants derive non-contiguous and misaligned views whose
|
| 1005 |
-
content equals a contiguous copy. `overflow_expand` stride-0-expands
|
| 1006 |
-
past 2**31 logical elements to catch int32 indexing (`skipped_vram`
|
| 1007 |
-
when it would not fit). Every case carries a `repro` recipe.
|
| 1008 |
-
In-process, the CUDA context is health-checked after exceptions and
|
| 1009 |
-
remaining cases marked `context_lost` if killed; `isolate=True`
|
| 1010 |
-
spawns each (shape, dtype) group in a subprocess, so a
|
| 1011 |
-
context-killing candidate costs one group (`crashed_isolated`);
|
| 1012 |
-
op/ref/make must be picklable."""
|
| 1013 |
if shapes is None:
|
| 1014 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1015 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
|
|
|
|
|
|
| 1016 |
|
| 1017 |
rows = []
|
| 1018 |
lost = False
|
| 1019 |
if isolate:
|
| 1020 |
import json
|
| 1021 |
import pickle
|
| 1022 |
-
import subprocess
|
| 1023 |
import sys as _sys
|
| 1024 |
import tempfile
|
| 1025 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1026 |
fns = os.path.join(tmpdir, "fns.pkl")
|
|
|
|
|
|
|
| 1027 |
with open(fns, "wb") as f:
|
| 1028 |
-
pickle.dump(
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
"shape": list(shape),
|
| 1041 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1042 |
"variants": list(variants),
|
| 1043 |
"ulp_tol": ulp_tol, "device": device,
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
try:
|
| 1048 |
-
r = subprocess.run(
|
| 1049 |
-
[_sys.executable, "-c", _ISOLATE_BOOTSTRAP, meta,
|
| 1050 |
-
fns, out], timeout=isolate_timeout_s,
|
| 1051 |
-
capture_output=True)
|
| 1052 |
-
if r.returncode == 0 and os.path.exists(out):
|
| 1053 |
with open(out, "rb") as f:
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
"status": "crashed_isolated",
|
| 1064 |
-
"exitcode": exitcode,
|
| 1065 |
-
"stderr_tail": stderr_tail}]
|
| 1066 |
-
rows.extend(group)
|
| 1067 |
else:
|
| 1068 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1069 |
for gi, (dtype, shape) in enumerate(pending):
|
|
@@ -1080,21 +1556,27 @@ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
|
|
| 1080 |
n_bad = sum(1 for r in rows
|
| 1081 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1082 |
"skipped_empty"))
|
| 1083 |
-
|
| 1084 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
|
| 1086 |
|
| 1087 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1088 |
check=None, budget_s=None, race=False):
|
| 1089 |
-
"""Launch-configuration search
|
| 1090 |
-
bench row per grid point, sorted fastest first. A point whose factory
|
| 1091 |
-
or run raises is reported, not skipped silently. `check(fn) -> bool`
|
| 1092 |
-
is an optional correctness gate recorded per row (a fast wrong config
|
| 1093 |
-
must not win a sweep). `budget_s` caps total sweep time; points not
|
| 1094 |
-
reached are reported as `not_run` rather than silently dropped.
|
| 1095 |
-
`race=True` runs successive halving: a quick pass over every point at
|
| 1096 |
-
iters/4, then the full budget only on the fastest quartile; pruned
|
| 1097 |
-
points keep their quick measurement and status `pruned_race`."""
|
| 1098 |
keys = list(grid.keys())
|
| 1099 |
|
| 1100 |
def expand(i, acc):
|
|
@@ -1159,13 +1641,71 @@ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
|
| 1159 |
continue
|
| 1160 |
rows.append(run_point(p, iters))
|
| 1161 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
return rows
|
| 1163 |
|
| 1164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1165 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1166 |
-
"""Device buffer for
|
| 1167 |
-
stamps.cuh): the candidate kernel writes clock64() into slot i. With
|
| 1168 |
-
`blocks`, allocates the [blocks, n] layout for MK_STAMP_BLOCK."""
|
| 1169 |
if blocks is not None:
|
| 1170 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1171 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
@@ -1175,8 +1715,7 @@ _DRIFT_SKIP = ("device", "clock", "launch_overhead", "transfer_bw")
|
|
| 1175 |
|
| 1176 |
|
| 1177 |
def pinned_dossiers():
|
| 1178 |
-
"""Names of the
|
| 1179 |
-
architecture)."""
|
| 1180 |
return sorted(_PINNED.keys())
|
| 1181 |
|
| 1182 |
|
|
@@ -1195,11 +1734,7 @@ def _numeric_leaves(d, prefix=""):
|
|
| 1195 |
|
| 1196 |
|
| 1197 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1198 |
-
"""
|
| 1199 |
-
same architecture: per-leaf relative drift, worst drift, entries
|
| 1200 |
-
over `rel_tol`. Host-dependent entries and raw clocks are skipped by
|
| 1201 |
-
default (`skip=()` compares everything); `pinned=None` auto-selects
|
| 1202 |
-
by sm_arch and raises if no pin exists."""
|
| 1203 |
if pinned is None:
|
| 1204 |
arch = current["device"]["sm_arch"]
|
| 1205 |
matches = [k for k, v in _PINNED.items()
|
|
@@ -1222,16 +1757,19 @@ def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
|
| 1222 |
"rel": round(rel, 4)})
|
| 1223 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1224 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
|
| 1229 |
|
| 1230 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1231 |
device="cuda"):
|
| 1232 |
-
"""Run the shipped MK_STAMP_BLOCK example
|
| 1233 |
-
compute, global store) and decode its stamps; a template for
|
| 1234 |
-
instrumenting a candidate."""
|
| 1235 |
props = torch.cuda.get_device_properties(device)
|
| 1236 |
if blocks is None:
|
| 1237 |
blocks = props.multi_processor_count
|
|
@@ -1244,11 +1782,7 @@ def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
|
| 1244 |
|
| 1245 |
|
| 1246 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1247 |
-
"""Decode MK_STAMP
|
| 1248 |
-
microseconds under the measured (or provided) SM clock. A 1-D buffer
|
| 1249 |
-
(MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
|
| 1250 |
-
buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
|
| 1251 |
-
phase, with blocks whose stamps stayed zero excluded."""
|
| 1252 |
if clock_ghz is None:
|
| 1253 |
clock_ghz = measure_clock(stamps.device)
|
| 1254 |
|
|
@@ -1284,3 +1818,104 @@ def read_stamps(stamps, labels=None, clock_ghz=None):
|
|
| 1284 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1285 |
"blocks_reporting": int(live.sum().item()),
|
| 1286 |
"blocks_total": int(live.numel())}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Measuring instrument for the kernel-design loop: measured device facts, throttle-aware timing, adversarial correctness comparison, all as loadable ops returning plain data."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import math
|
| 3 |
import os
|
| 4 |
import time
|
|
|
|
| 8 |
try:
|
| 9 |
from ._ops import ops
|
| 10 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 11 |
+
# children re-import the package; a source checkout rebuilds the op table from the cached JIT extension
|
|
|
|
|
|
|
| 12 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 13 |
if not _root:
|
| 14 |
raise
|
|
|
|
| 30 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 31 |
_PINNED = {}
|
| 32 |
|
| 33 |
+
__all__ = ["probe_device", "probe_all_devices", "bench", "compare", "judge",
|
| 34 |
+
"order_shuffles", "fuzz_shapes", "sweep", "alloc_stamps",
|
| 35 |
+
"read_stamps", "stamped_demo", "measure_clock", "ulp_diff",
|
| 36 |
+
"dossier_drift", "pinned_dossiers", "mma_tile_check", "schema",
|
| 37 |
+
"probe_capturable", "ops"]
|
| 38 |
+
|
| 39 |
+
_REPO_ID = "phanerozoic/metakernel"
|
| 40 |
+
_REPO_VERSION = 1
|
| 41 |
+
_PROBE_SCHEMA = 2
|
| 42 |
+
|
| 43 |
+
# kind id, C-tile M*N, nominal K, nominal K-lanes per unrolled loop iteration
|
| 44 |
_MMA_KINDS = {
|
| 45 |
+
"fp16": (0, 16 * 16, 16, 64.0),
|
| 46 |
+
"bf16": (1, 16 * 16, 16, 64.0),
|
| 47 |
+
"tf32": (2, 16 * 16, 8, 32.0),
|
| 48 |
+
"int8": (3, 16 * 16, 16, 64.0),
|
| 49 |
+
"fp8_e4m3": (4, 16 * 8, 32, 256.0),
|
| 50 |
+
"fp16_sparse_2_4": (5, 16 * 8, 32, 128.0),
|
| 51 |
+
"fp8_e5m2": (6, 16 * 8, 32, 256.0),
|
| 52 |
}
|
| 53 |
|
| 54 |
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def _events(fn, reps):
|
| 60 |
+
"""Time reps calls of fn with one cudaEvent pair each; returns a ms list."""
|
| 61 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 62 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 63 |
for s, e in zip(starts, stops):
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 72 |
+
"""Effective SM clock in GHz from clock64 ticks over wall time under an FMA spin."""
|
|
|
|
| 73 |
props = torch.cuda.get_device_properties(device)
|
| 74 |
if blocks is None:
|
| 75 |
blocks = props.multi_processor_count
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
def _sattolo_ring(n, device, generator=None):
|
| 105 |
+
"""Random single-cycle permutation as an int32 next-pointer table."""
|
|
|
|
| 106 |
perm = torch.randperm(n, generator=generator)
|
| 107 |
ring = torch.empty(n, dtype=torch.int32)
|
| 108 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
|
|
| 110 |
|
| 111 |
|
| 112 |
def _smi_settings(index):
|
| 113 |
+
"""ECC mode, driver, power limit via nvidia-smi; None fields when unavailable."""
|
|
|
|
| 114 |
import subprocess
|
| 115 |
try:
|
| 116 |
out = subprocess.run(
|
|
|
|
| 130 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 131 |
|
| 132 |
|
| 133 |
+
def _unravel(flat, shape):
|
| 134 |
+
"""Multi-dimensional index for a flat index into shape."""
|
| 135 |
+
if not shape:
|
| 136 |
+
return []
|
| 137 |
+
out = []
|
| 138 |
+
for s in reversed(list(shape)):
|
| 139 |
+
out.append(int(flat % s))
|
| 140 |
+
flat //= s
|
| 141 |
+
return list(reversed(out))
|
| 142 |
+
|
| 143 |
+
|
| 144 |
def mma_tile_check(device="cuda", trials=8):
|
| 145 |
+
"""Random small-integer m16n16k16 tiles through the wmma path, compared exactly to the reference GEMM."""
|
|
|
|
|
|
|
| 146 |
worst = 0.0
|
| 147 |
for t in range(trials):
|
| 148 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
|
|
| 160 |
"trials": trials}
|
| 161 |
|
| 162 |
|
| 163 |
+
def _tenancy_canary(device, rel_tol=0.10, attempts=3):
|
| 164 |
+
"""Fixed-cost triad timed in paired rounds; busy only when every attempt disagrees."""
|
| 165 |
+
n4 = 4 << 20
|
| 166 |
+
a = torch.rand(n4 * 4, device=device)
|
| 167 |
+
b = torch.rand(n4 * 4, device=device)
|
| 168 |
+
c = torch.empty(n4 * 4, device=device)
|
| 169 |
+
ops.mk_triad(a, b, c, 4, 1.5)
|
| 170 |
+
rels = []
|
| 171 |
+
for _ in range(attempts):
|
| 172 |
+
r1 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 173 |
+
r2 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 174 |
+
rel = abs(r1 - r2) / min(r1, r2)
|
| 175 |
+
rels.append(round(rel, 4))
|
| 176 |
+
if rel <= rel_tol:
|
| 177 |
+
return False, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 178 |
+
return True, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _cache_dir():
|
| 182 |
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~/.cache")
|
| 183 |
+
d = os.path.join(base, "metakernel")
|
| 184 |
+
os.makedirs(d, exist_ok=True)
|
| 185 |
+
return d
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _cache_path(device, quick):
|
| 189 |
+
import re
|
| 190 |
+
props = torch.cuda.get_device_properties(device)
|
| 191 |
+
smi = _smi_settings(torch.device(device).index or 0)
|
| 192 |
+
slug = re.sub(r"[^a-z0-9]+", "", props.name.lower())[:24]
|
| 193 |
+
key = "sm%d%d_%s_drv%s_cu%s_q%d_s%d" % (
|
| 194 |
+
props.major, props.minor, slug, smi["driver"] or "na",
|
| 195 |
+
torch.version.cuda, int(quick), _PROBE_SCHEMA)
|
| 196 |
+
return os.path.join(_cache_dir(), key + ".json")
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _mma_calibrate(kind_id, sink):
|
| 200 |
+
"""Executed K-lanes per loop iteration from a sub-saturation accumulator read-back, with a linearity check."""
|
| 201 |
+
gains = []
|
| 202 |
+
for cal in (4, 32):
|
| 203 |
+
sink[1] = 0.0
|
| 204 |
+
ops.mk_mma(kind_id, 1, 1, cal, sink)
|
| 205 |
+
_sync()
|
| 206 |
+
gains.append(float(sink[1].item()) / cal)
|
| 207 |
+
linear = gains[0] > 0 and abs(gains[1] - gains[0]) / gains[0] < 0.02
|
| 208 |
+
return gains[0], linear
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _fp8_accum_stall(kind_id, sink, k_exec):
|
| 212 |
+
"""Growth curve of the fp8 accumulator; a stall reveals the effective accumulate mantissa width."""
|
| 213 |
+
vals = []
|
| 214 |
+
for it in (32, 64, 128, 256, 512):
|
| 215 |
+
sink[1] = 0.0
|
| 216 |
+
ops.mk_mma(kind_id, 1, 1, it, sink)
|
| 217 |
+
_sync()
|
| 218 |
+
vals.append(float(sink[1].item()) / 2.0)
|
| 219 |
+
if vals[-1] == vals[-2] and vals[-1] < vals[0] * 16 * 0.99:
|
| 220 |
+
bits = int(round(math.log2(vals[-1] / (2.0 * k_exec))))
|
| 221 |
+
return {"stall_element_value": vals[-1],
|
| 222 |
+
"effective_accum_mantissa_bits": bits}
|
| 223 |
+
return {"stall_element_value": None,
|
| 224 |
+
"effective_accum_mantissa_bits": None}
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def probe_device(device="cuda", quick=False, cached=True,
|
| 228 |
+
tenancy_check=True):
|
| 229 |
+
"""Empirical device dossier; every entry measured by a dedicated probe kernel and stamped with its SM clock."""
|
| 230 |
+
cpath = _cache_path(device, quick)
|
| 231 |
+
if cached and os.path.exists(cpath):
|
| 232 |
+
import json
|
| 233 |
+
return json.load(open(cpath, encoding="utf-8"))
|
| 234 |
+
if tenancy_check:
|
| 235 |
+
busy, ev = _tenancy_canary(device)
|
| 236 |
+
if busy:
|
| 237 |
+
raise RuntimeError("device_busy: canary rounds disagree %s; "
|
| 238 |
+
"idle the device or pass "
|
| 239 |
+
"tenancy_check=False" % ev)
|
| 240 |
props = torch.cuda.get_device_properties(device)
|
| 241 |
sm = props.multi_processor_count
|
| 242 |
l2 = props.L2_cache_size
|
|
|
|
| 249 |
"vram_bytes": props.total_memory,
|
| 250 |
"torch": torch.__version__,
|
| 251 |
"cuda_runtime": torch.version.cuda,
|
| 252 |
+
# instrument settings recorded because readings depend on them
|
|
|
|
| 253 |
**_smi_settings(torch.device(device).index or 0),
|
| 254 |
}
|
| 255 |
}
|
|
|
|
| 274 |
}
|
| 275 |
clk = max(traj)
|
| 276 |
|
| 277 |
+
# ---- device-memory bandwidth per access width plus read/write/gather
|
| 278 |
+
n4 = 16 << 20
|
|
|
|
| 279 |
if props.total_memory < 4 << 30:
|
| 280 |
n4 = 4 << 20
|
| 281 |
a = torch.rand(n4 * 4, device=device)
|
|
|
|
| 299 |
dtype=torch.int32).to(device)
|
| 300 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 301 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
|
|
|
| 302 |
hbm["random_gather_16B"] = round(
|
| 303 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 304 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
|
|
| 309 |
"so its nominal number is the achievable rate for "
|
| 310 |
"random access, not a line-efficiency claim")
|
| 311 |
|
| 312 |
+
# ---- L2 bandwidth over an L2-resident working set
|
| 313 |
ws = max(l2 // 4, 1 << 20)
|
| 314 |
+
nl2 = ws // 16
|
| 315 |
al = a[:nl2 * 4].contiguous()
|
| 316 |
bl = b[:nl2 * 4].contiguous()
|
| 317 |
cl = c[:nl2 * 4].contiguous()
|
|
|
|
| 326 |
"medium", "first pass streams from HBM; amortized "
|
| 327 |
f"over {passes} passes")
|
| 328 |
|
| 329 |
+
# ---- latencies: min of 3 chases per level so a co-tenant burst cannot land in the dossier
|
|
|
|
|
|
|
| 330 |
lat = {}
|
| 331 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
+
def chase(op, ring, warm_hops, hops, extra=None):
|
| 334 |
+
best = None
|
| 335 |
+
for _ in range(3):
|
| 336 |
+
if extra is None:
|
| 337 |
+
op(ring, warm_hops, out64)
|
| 338 |
+
_sync()
|
| 339 |
+
op(ring, hops, out64)
|
| 340 |
+
else:
|
| 341 |
+
op(ring, extra, warm_hops, out64)
|
| 342 |
+
_sync()
|
| 343 |
+
op(ring, extra, hops, out64)
|
| 344 |
+
_sync()
|
| 345 |
+
t = int(out64[0].item()) / hops
|
| 346 |
+
best = t if best is None else min(best, t)
|
| 347 |
+
return best
|
|
|
|
|
|
|
| 348 |
|
| 349 |
+
ring_s = _sattolo_ring(8192, device)
|
| 350 |
+
lat["smem_cycles"] = round(
|
| 351 |
+
chase(lambda r, h, o: ops.mk_chase_shared(r, h, o), ring_s,
|
| 352 |
+
1 << 14, 1 << 16), 1)
|
| 353 |
+
ring_l1 = _sattolo_ring((16 << 10) // 4, device)
|
| 354 |
+
lat["l1_cycles"] = round(
|
| 355 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l1,
|
| 356 |
+
ring_l1.numel(), 1 << 15), 1)
|
| 357 |
+
ring_l2 = _sattolo_ring((2 << 20) // 4, device)
|
| 358 |
+
lat["l2_cycles"] = round(
|
| 359 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l2,
|
| 360 |
+
ring_l2.numel(), 1 << 14), 1)
|
| 361 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 362 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 363 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 364 |
+
lat["dram_cycles"] = round(
|
| 365 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_h,
|
| 366 |
+
1 << 14, hops_h), 1)
|
|
|
|
|
|
|
| 367 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 368 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 369 |
+
"Sattolo-cycle pointer chase, min of 3 runs; 16 "
|
| 370 |
+
"KB ring for L1, 2 MB TLB-resident ring for L2, "
|
| 371 |
+
"self-evicting chain (2x L2 in touched lines) "
|
| 372 |
+
"for HBM", idle_clk, "high",
|
| 373 |
"smem includes index arithmetic; HBM includes "
|
| 374 |
"residual TLB effects at large footprints")
|
| 375 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 376 |
|
| 377 |
+
# ---- fp32/fp64 FMA and tensor-core throughput
|
| 378 |
blocks = sm * 4
|
| 379 |
iters = 20_000 if quick else 60_000
|
| 380 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 381 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 382 |
reps))
|
| 383 |
+
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 384 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 385 |
"register-resident FMA chains", clk, "high")
|
| 386 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
|
|
| 392 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 393 |
"register-resident double FMA chains (the DFMA "
|
| 394 |
"pipe)", clk, "high")
|
| 395 |
+
|
| 396 |
+
# ---- ILP ladder: 1/2/4 chains at low occupancy separates latency-bound from pipe-bound
|
| 397 |
+
ilp = {}
|
| 398 |
+
it_ilp = 20_000 if quick else 60_000
|
| 399 |
+
for ch in (1, 2, 4):
|
| 400 |
+
ops.mk_fma_ilp(ch, sm, 64, it_ilp, sink)
|
| 401 |
+
ms = _median(_events(
|
| 402 |
+
lambda c2=ch: ops.mk_fma_ilp(c2, sm, 64, it_ilp, sink), reps))
|
| 403 |
+
ilp[f"ilp{ch}"] = round(
|
| 404 |
+
sm * 64 * it_ilp * 16 * 2 * ch / (ms * 1e-3) / 1e12, 2)
|
| 405 |
+
ilp["ratio_4_1"] = round(ilp["ilp4"] / ilp["ilp1"], 2)
|
| 406 |
+
d["fma_ilp"] = _entry(ilp, "TFLOP/s",
|
| 407 |
+
"1/2/4 independent dependent FMA chains per "
|
| 408 |
+
"thread at one block of 64 threads per SM; a "
|
| 409 |
+
"ratio near 4 means latency-bound at that "
|
| 410 |
+
"occupancy", clk, "high")
|
| 411 |
+
|
| 412 |
mma = {}
|
| 413 |
mma_klanes = {}
|
| 414 |
+
fp8_accum = {}
|
| 415 |
+
for name, (kind, mn, k_nom, nom_lanes) in _MMA_KINDS.items():
|
| 416 |
it = 30_000 if quick else 100_000
|
| 417 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 418 |
if r != 0:
|
| 419 |
mma[name] = "unsupported"
|
| 420 |
continue
|
| 421 |
_sync()
|
| 422 |
+
gain, linear = _mma_calibrate(kind, sink)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
mma_klanes[name] = {
|
| 424 |
+
"executed_k_per_iter": round(gain, 2),
|
| 425 |
+
"vs_nominal": round(gain / nom_lanes, 3),
|
| 426 |
+
"calibration_linear": linear,
|
| 427 |
}
|
| 428 |
+
if name.startswith("fp8"):
|
| 429 |
+
fp8_accum[name] = _fp8_accum_stall(kind, sink, k_nom)
|
| 430 |
ms = _median(_events(
|
| 431 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 432 |
+
tflops = blocks * 8 * it * 2.0 * mn * gain / (ms * 1e-3) / 1e12
|
| 433 |
+
if name == "fp16_sparse_2_4":
|
| 434 |
+
mma[name] = {"executed": round(tflops, 1),
|
| 435 |
+
"dense_equivalent": round(2 * tflops, 1)}
|
| 436 |
+
else:
|
| 437 |
+
mma[name] = round(tflops, 1)
|
| 438 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 439 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 440 |
+
"2:4-sparse fp16 via inline PTX m16n8k32); flops "
|
| 441 |
+
"derive from a sub-saturation accumulator read-back "
|
|
|
|
| 442 |
"with every fragment element observed, never the "
|
| 443 |
+
"declared loop shape; sparse reports executed and "
|
| 444 |
+
"dense-equivalent flops",
|
| 445 |
clk, "medium",
|
| 446 |
"dense-tile rate without memory traffic; real "
|
| 447 |
"kernels see this only when staging hides behind it")
|
| 448 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
| 449 |
+
if fp8_accum:
|
| 450 |
+
d["mma"]["fp8_accum"] = fp8_accum
|
| 451 |
|
| 452 |
+
# ---- fed MMA: smem-staged fp16 tiles under cp.async double buffering
|
| 453 |
+
if props.major >= 8:
|
| 454 |
+
gbuf = torch.ones(16 << 20, dtype=torch.float16, device=device)
|
| 455 |
+
sink[1] = 0.0
|
| 456 |
+
r = ops.mk_mma_fed(gbuf, 1, 1, 64, sink)
|
| 457 |
+
if r == 0:
|
| 458 |
+
_sync()
|
| 459 |
+
fed_gain = float(sink[1].item()) / 64
|
| 460 |
+
it_fed = 6_000 if quick else 20_000
|
| 461 |
+
ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink)
|
| 462 |
+
ms = _median(_events(
|
| 463 |
+
lambda: ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink),
|
| 464 |
+
reps))
|
| 465 |
+
fed = sm * 2 * 8 * it_fed * 2.0 * 256 * fed_gain / \
|
| 466 |
+
(ms * 1e-3) / 1e12
|
| 467 |
+
d["mma_fed"] = _entry(
|
| 468 |
+
round(fed, 1), "TFLOP/s",
|
| 469 |
+
"m16n16k16 fp16 tiles fed from an L2-resident buffer "
|
| 470 |
+
"through cp.async double-buffered shared memory; "
|
| 471 |
+
"accumulator-verified", clk, "high",
|
| 472 |
+
"the achieved counterpart to the register peak: staging "
|
| 473 |
+
"exposed, one dependent accumulator chain per warp")
|
| 474 |
+
del gbuf
|
| 475 |
+
|
| 476 |
+
# ---- shared-memory bandwidth and the bank-conflict stride sweep
|
| 477 |
it_sm = 30_000 if quick else 100_000
|
| 478 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 479 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
|
|
| 483 |
"block-resident read+write streaming (8 B per "
|
| 484 |
"thread-iteration), aggregated across SMs", clk,
|
| 485 |
"medium",
|
| 486 |
+
"bank-conflict-light access pattern; the stride "
|
| 487 |
+
"sweep below prices the conflicted case")
|
| 488 |
+
strides = (1, 8, 32) if quick else (1, 2, 4, 8, 16, 32)
|
| 489 |
+
it_st = 8_000 if quick else 20_000
|
| 490 |
+
st_ms = {}
|
| 491 |
+
for s2 in strides:
|
| 492 |
+
ops.mk_smem_stride(s2, blocks, 256, it_st, sink)
|
| 493 |
+
st_ms[s2] = _median(_events(
|
| 494 |
+
lambda s3=s2: ops.mk_smem_stride(s3, blocks, 256, it_st, sink),
|
| 495 |
+
reps))
|
| 496 |
+
d["smem_conflict_penalty"] = _entry(
|
| 497 |
+
{f"stride_{s2}": round(st_ms[s2] / st_ms[1], 2) for s2 in strides},
|
| 498 |
+
"x (time ratio vs stride 1)",
|
| 499 |
+
"dependent read-FMA-write per thread at warp-lane bank distance "
|
| 500 |
+
"`stride`; stride 32 puts every lane in one bank", clk, "high",
|
| 501 |
+
"the direct conflict measurement that disambiguates a "
|
| 502 |
+
"bank-conflict signature from an ILP stall")
|
| 503 |
|
| 504 |
# ---- PCIe / host transfer bandwidth
|
| 505 |
nbytes = 256 << 20
|
|
|
|
| 530 |
"d2d counts one direction of traffic")
|
| 531 |
del host_pin, host_pag, dev_t, dev_u
|
| 532 |
|
| 533 |
+
# ---- atomic contention curve
|
|
|
|
|
|
|
| 534 |
n_thr = blocks * 256
|
| 535 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 536 |
at = {"curve_slots": {}}
|
|
|
|
| 540 |
buf.zero_()
|
| 541 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 542 |
ms = _median(_events(
|
| 543 |
+
lambda s4=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s4),
|
| 544 |
reps))
|
| 545 |
at["curve_slots"][str(slots)] = round(
|
| 546 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
|
|
| 553 |
"of distinct global addresses from 1 (full "
|
| 554 |
"contention) to one per thread", clk, "high")
|
| 555 |
|
| 556 |
+
# ---- launch overhead: torch-dispatch path and the raw C++ path
|
| 557 |
anchor = sink
|
| 558 |
n_launch = 500 if quick else 2000
|
| 559 |
_sync()
|
|
|
|
| 568 |
ops.mk_empty(anchor)
|
| 569 |
_sync()
|
| 570 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
| 571 |
+
raw_q = float(ops.mk_launch_raw(n_launch, 0, anchor))
|
| 572 |
+
raw_rt = float(ops.mk_launch_raw(n_rt, 1, anchor))
|
| 573 |
d["launch_overhead"] = _entry(
|
| 574 |
+
{"torch_queued_us": round(queued, 2),
|
| 575 |
+
"torch_round_trip_us": round(rt, 2),
|
| 576 |
+
"raw_queued_us": round(raw_q / n_launch * 1e6, 2)
|
| 577 |
+
if raw_q >= 0 else None,
|
| 578 |
+
"raw_round_trip_us": round(raw_rt / n_rt * 1e6, 2)
|
| 579 |
+
if raw_rt >= 0 else None},
|
| 580 |
+
"us", "empty-kernel trains; torch path per-launch through op "
|
| 581 |
+
"dispatch, raw path looped inside one binding call", clk,
|
| 582 |
+
"medium", "host-state-dependent; the torch-minus-raw gap is the "
|
| 583 |
+
"dispatch cost a torch-launched candidate pays")
|
| 584 |
|
| 585 |
# ---- grid barrier
|
| 586 |
bar_iters = 500 if quick else 2000
|
|
|
|
| 596 |
"unavailable or grid too large", clk,
|
| 597 |
"high")
|
| 598 |
|
| 599 |
+
# ---- occupancy API table plus timed knees
|
|
|
|
|
|
|
| 600 |
occ = {}
|
| 601 |
for threads in (128, 256, 512, 1024):
|
| 602 |
row = {}
|
|
|
|
| 617 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 618 |
knee_iters, sink)
|
| 619 |
ms = _median(_events(
|
| 620 |
+
lambda t=threads, s5=smem_kb: ops.mk_fma_dep(
|
| 621 |
+
knee_blocks, t, s5 * 1024, knee_iters, sink), reps))
|
| 622 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 623 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 624 |
knees[f"t{threads}"] = row
|
|
|
|
| 627 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 628 |
"the cliff marks where occupancy stops covering latency",
|
| 629 |
clk, "high", "timing-derived counterpart to the API table above")
|
| 630 |
+
if cached:
|
| 631 |
+
import json
|
| 632 |
+
json.dump(d, open(cpath, "w", encoding="utf-8"))
|
| 633 |
return d
|
| 634 |
|
| 635 |
|
| 636 |
+
def probe_all_devices(quick=True, cached=True, tenancy_check=True):
|
| 637 |
+
"""One dossier per visible device plus a pairwise P2P bandwidth matrix."""
|
| 638 |
+
n = torch.cuda.device_count()
|
| 639 |
+
devices = {}
|
| 640 |
+
for i in range(n):
|
| 641 |
+
devices[i] = probe_device(f"cuda:{i}", quick=quick, cached=cached,
|
| 642 |
+
tenancy_check=tenancy_check)
|
| 643 |
+
p2p = {}
|
| 644 |
+
if n > 1:
|
| 645 |
+
nbytes = 64 << 20
|
| 646 |
+
for i in range(n):
|
| 647 |
+
for j in range(n):
|
| 648 |
+
if i == j:
|
| 649 |
+
continue
|
| 650 |
+
src = torch.rand(nbytes // 4, device=f"cuda:{i}")
|
| 651 |
+
dst = torch.empty(nbytes // 4, device=f"cuda:{j}")
|
| 652 |
+
dst.copy_(src)
|
| 653 |
+
torch.cuda.synchronize(i)
|
| 654 |
+
torch.cuda.synchronize(j)
|
| 655 |
+
t0 = time.perf_counter()
|
| 656 |
+
for _ in range(5):
|
| 657 |
+
dst.copy_(src)
|
| 658 |
+
torch.cuda.synchronize(i)
|
| 659 |
+
torch.cuda.synchronize(j)
|
| 660 |
+
ms = (time.perf_counter() - t0) / 5 * 1e3
|
| 661 |
+
p2p[f"{i}->{j}"] = {
|
| 662 |
+
"gbs": round(nbytes / (ms * 1e-3) / 1e9, 1),
|
| 663 |
+
"peer_access": bool(
|
| 664 |
+
torch.cuda.can_device_access_peer(i, j)),
|
| 665 |
+
}
|
| 666 |
+
del src, dst
|
| 667 |
+
return {"devices": devices, "p2p": p2p}
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
_CAPTURE_PROBE = r"""
|
| 671 |
+
import pickle, sys, torch
|
| 672 |
+
fn = pickle.load(open(sys.argv[1], "rb"))
|
| 673 |
+
for _ in range(3):
|
| 674 |
+
fn()
|
| 675 |
+
torch.cuda.synchronize()
|
| 676 |
+
try:
|
| 677 |
+
g = torch.cuda.CUDAGraph()
|
| 678 |
+
with torch.cuda.graph(g):
|
| 679 |
+
fn()
|
| 680 |
+
print("capturable", flush=True)
|
| 681 |
+
except Exception as e:
|
| 682 |
+
print("uncapturable: %s: %s" % (type(e).__name__, str(e)[:120]),
|
| 683 |
+
flush=True)
|
| 684 |
+
"""
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def probe_capturable(fn, timeout_s=180):
|
| 688 |
+
"""Test CUDA-graph capturability of fn in a child process, so a capture-wedged RNG never reaches the caller."""
|
| 689 |
+
import pickle
|
| 690 |
+
import subprocess
|
| 691 |
+
import sys as _sys
|
| 692 |
+
import tempfile
|
| 693 |
+
try:
|
| 694 |
+
blob = pickle.dumps(fn)
|
| 695 |
+
except Exception: # noqa: BLE001
|
| 696 |
+
return {"capturable": None,
|
| 697 |
+
"reason": "fn is not picklable; probe skipped (define fn "
|
| 698 |
+
"at module top level to enable it)"}
|
| 699 |
+
path = os.path.join(tempfile.mkdtemp(prefix="mk_cap_"), "fn.pkl")
|
| 700 |
+
with open(path, "wb") as f:
|
| 701 |
+
f.write(blob)
|
| 702 |
+
try:
|
| 703 |
+
r = subprocess.run([_sys.executable, "-c", _CAPTURE_PROBE, path],
|
| 704 |
+
capture_output=True, text=True,
|
| 705 |
+
timeout=timeout_s)
|
| 706 |
+
except Exception as e: # noqa: BLE001
|
| 707 |
+
return {"capturable": None,
|
| 708 |
+
"reason": f"probe child failed: {type(e).__name__}"}
|
| 709 |
+
out = (r.stdout or "").strip()
|
| 710 |
+
if out == "capturable":
|
| 711 |
+
return {"capturable": True, "reason": ""}
|
| 712 |
+
if out.startswith("uncapturable"):
|
| 713 |
+
return {"capturable": False, "reason": out}
|
| 714 |
+
return {"capturable": None,
|
| 715 |
+
"reason": "probe child exit %s: %s" % (
|
| 716 |
+
r.returncode, (r.stderr or "")[-200:])}
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
def _bench_verdict(rep):
|
| 720 |
+
parts = ["median %.4g ms" % rep["median_ms"]]
|
| 721 |
+
if "achieved_tflops" in rep:
|
| 722 |
+
parts.append("%.4g TFLOP/s" % rep["achieved_tflops"])
|
| 723 |
+
elif "achieved_gbs" in rep:
|
| 724 |
+
parts.append("%.4g GB/s" % rep["achieved_gbs"])
|
| 725 |
+
if "roofline" in rep:
|
| 726 |
+
parts.append("%.0f%% of %s roof" % (rep["roofline"]["pct_of_roof"],
|
| 727 |
+
rep["roofline"]["bound"]))
|
| 728 |
+
if rep["rejected_throttled"]:
|
| 729 |
+
parts.append("%d/%d rejected (%s)" % (
|
| 730 |
+
rep["rejected_throttled"], rep["samples"],
|
| 731 |
+
rep["rejection_basis"]))
|
| 732 |
+
if rep.get("no_clean_window"):
|
| 733 |
+
parts.append("no clean window: the whole run was throttled")
|
| 734 |
+
if rep.get("alloc_growth_bytes_per_call"):
|
| 735 |
+
parts.append("fn allocates %d B/call" %
|
| 736 |
+
rep["alloc_growth_bytes_per_call"])
|
| 737 |
+
if rep.get("host_overhead_suspected"):
|
| 738 |
+
parts.append("fn appears to sync or do host work inside")
|
| 739 |
+
return "; ".join(parts)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 743 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 744 |
+
graph_probe_isolated=False, device="cuda"):
|
| 745 |
+
"""Median-of-N cudaEvent timing with throttle rejection, roofline placement, and footgun detection."""
|
| 746 |
+
for name, v in (("iters", iters), ("warmup", warmup)):
|
| 747 |
+
if not isinstance(v, int) or v < (1 if name == "iters" else 0):
|
| 748 |
+
raise ValueError(f"{name} must be a non-negative int; "
|
| 749 |
+
f"got {v!r}")
|
| 750 |
+
for name, v in (("bytes", bytes), ("flops", flops)):
|
| 751 |
+
if v is not None and (not isinstance(v, (int, float)) or v <= 0):
|
| 752 |
+
raise ValueError(f"{name} must be a positive number counting "
|
| 753 |
+
f"{name} per call of fn; got {v!r}")
|
|
|
|
| 754 |
for _ in range(warmup):
|
| 755 |
fn()
|
| 756 |
_sync()
|
| 757 |
+
# footgun probes: per-call allocation growth and hidden host work
|
| 758 |
+
alloc0 = torch.cuda.memory_allocated(device)
|
| 759 |
+
fn()
|
| 760 |
+
fn()
|
| 761 |
+
_sync()
|
| 762 |
+
alloc_growth = max(0, (torch.cuda.memory_allocated(device) - alloc0)
|
| 763 |
+
// 2)
|
| 764 |
+
# a burst of enqueues that leaves the stream idle means fn drains it; only trustworthy above the launch-overhead floor
|
| 765 |
+
for _ in range(16):
|
| 766 |
+
fn()
|
| 767 |
+
stream_idle = bool(torch.cuda.current_stream(device).query())
|
| 768 |
+
_sync()
|
| 769 |
+
t0 = time.perf_counter()
|
| 770 |
+
pre = _events(fn, 4)
|
| 771 |
+
wall_ms = (time.perf_counter() - t0) * 1e3
|
| 772 |
+
ev_ms = sum(pre)
|
| 773 |
+
host_suspect = (stream_idle and ev_ms / 4 > 0.05) or \
|
| 774 |
+
wall_ms > ev_ms * 1.5 + 2.0
|
| 775 |
+
|
| 776 |
graph_state = "off"
|
| 777 |
graph_reason = ""
|
| 778 |
+
if graph and graph_probe_isolated:
|
| 779 |
+
cap = probe_capturable(fn)
|
| 780 |
+
if cap["capturable"] is False:
|
| 781 |
+
graph = False
|
| 782 |
+
graph_state = "fallback_eager"
|
| 783 |
+
graph_reason = "isolated probe: " + cap["reason"]
|
| 784 |
if graph:
|
| 785 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 786 |
try:
|
|
|
|
| 795 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 796 |
graph_state = "fallback_eager"
|
| 797 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 798 |
+
# drain the invalidated stream; a failed capture can wedge the device RNG on some torch versions
|
|
|
|
|
|
|
|
|
|
| 799 |
rng_ok = True
|
| 800 |
for _attempt in range(5):
|
| 801 |
try:
|
|
|
|
| 825 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 826 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 827 |
best_clk = max(clocks)
|
| 828 |
+
|
| 829 |
+
# rejection basis: memory-bound work is judged on its own rate, not the SM clock
|
| 830 |
+
ridge_memory = False
|
| 831 |
+
if dossier is not None and bytes and flops:
|
| 832 |
+
bw0 = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 833 |
+
mma_v0 = dossier["mma"]["value"].get(dtype)
|
| 834 |
+
if isinstance(mma_v0, dict):
|
| 835 |
+
mma_v0 = mma_v0.get("dense_equivalent")
|
| 836 |
+
peak0 = (mma_v0 if isinstance(mma_v0, (int, float))
|
| 837 |
+
else dossier["fma_f32"]["value"]) * 1e12
|
| 838 |
+
ridge_memory = (flops / bytes) * bw0 < peak0
|
| 839 |
+
basis = "bandwidth_consistency" if (
|
| 840 |
+
bytes is not None and (flops is None or ridge_memory)) \
|
| 841 |
+
else "sm_clock"
|
| 842 |
+
|
| 843 |
flat = []
|
| 844 |
+
kept = []
|
| 845 |
+
rejected = 0
|
| 846 |
+
if basis == "sm_clock":
|
| 847 |
+
for group, ci in samples:
|
| 848 |
+
window_ok = (clocks[ci] >= clock_guard * best_clk and
|
| 849 |
+
clocks[min(ci + 1, len(clocks) - 1)] >=
|
| 850 |
+
clock_guard * best_clk)
|
| 851 |
+
for ms in group:
|
| 852 |
+
flat.append(ms)
|
| 853 |
+
if window_ok:
|
| 854 |
+
kept.append(ms)
|
| 855 |
+
else:
|
| 856 |
+
rejected += 1
|
| 857 |
+
else:
|
| 858 |
+
for group, _ci in samples:
|
| 859 |
+
flat.extend(group)
|
| 860 |
+
floor = sorted(flat)[max(0, int(0.05 * len(flat)) - 1)]
|
| 861 |
+
for ms in flat:
|
| 862 |
+
if ms <= floor * 1.15:
|
| 863 |
kept.append(ms)
|
| 864 |
else:
|
| 865 |
rejected += 1
|
| 866 |
+
all_rejected = not kept
|
| 867 |
+
if all_rejected:
|
| 868 |
kept = flat
|
| 869 |
med = _median(kept)
|
| 870 |
ks = sorted(kept)
|
| 871 |
n_k = len(ks)
|
| 872 |
+
# order-statistic 95% CI on the median; degenerate at tiny n
|
|
|
|
| 873 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 874 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 875 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
|
|
| 884 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 885 |
"samples": len(flat),
|
| 886 |
"rejected_throttled": rejected,
|
| 887 |
+
"rejection_basis": basis,
|
| 888 |
"graph_captured": graph_state == "captured",
|
| 889 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 890 |
"worst": round(min(clocks), 3)},
|
| 891 |
}
|
| 892 |
+
no_clean = all_rejected
|
| 893 |
+
if basis == "sm_clock" and dossier is not None:
|
| 894 |
+
ref_clk = dossier["clock"]["max_ghz"]
|
| 895 |
+
if best_clk < clock_guard * ref_clk:
|
| 896 |
+
no_clean = True
|
| 897 |
+
if no_clean:
|
| 898 |
+
rep["no_clean_window"] = True
|
| 899 |
if graph_reason:
|
| 900 |
rep["graph_fallback_reason"] = graph_reason
|
| 901 |
+
if alloc_growth:
|
| 902 |
+
rep["alloc_growth_bytes_per_call"] = int(alloc_growth)
|
| 903 |
+
if host_suspect:
|
| 904 |
+
rep["host_overhead_suspected"] = True
|
| 905 |
+
rep["host_wall_over_event"] = round(wall_ms / max(ev_ms, 1e-9), 2)
|
| 906 |
if bytes is not None:
|
| 907 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 908 |
if flops is not None:
|
|
|
|
| 912 |
if dossier is not None and bytes and flops:
|
| 913 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 914 |
mma_v = dossier["mma"]["value"].get(dtype)
|
| 915 |
+
if isinstance(mma_v, dict):
|
| 916 |
+
mma_v = mma_v.get("dense_equivalent")
|
| 917 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 918 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 919 |
ai = flops / bytes
|
|
|
|
| 924 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 925 |
"peak_dtype": dtype,
|
| 926 |
}
|
| 927 |
+
rep["verdict"] = _bench_verdict(rep)
|
| 928 |
return rep
|
| 929 |
|
| 930 |
|
| 931 |
def _to_ordered_int(t):
|
| 932 |
+
"""Monotone integer mapping of float bit patterns; differences are ULP distances."""
|
|
|
|
|
|
|
| 933 |
if t.dtype == torch.float32:
|
| 934 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 935 |
half = 1 << 31
|
|
|
|
| 945 |
|
| 946 |
|
| 947 |
def ulp_diff(a, b):
|
| 948 |
+
"""Elementwise ULP distance; both-NaN counts 0, one-sided NaN 2**62, integers by absolute difference."""
|
| 949 |
+
if a.dtype != b.dtype or a.shape != b.shape:
|
| 950 |
+
raise ValueError("ulp_diff needs matching dtype and shape; got "
|
| 951 |
+
f"{a.dtype}{tuple(a.shape)} vs "
|
| 952 |
+
f"{b.dtype}{tuple(b.shape)}; cast or reshape the "
|
| 953 |
+
"candidate to the reference layout")
|
| 954 |
if not a.is_floating_point():
|
| 955 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 956 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
|
|
| 967 |
|
| 968 |
@staticmethod
|
| 969 |
def last_dim():
|
| 970 |
+
"""Identical permutation of every arg's last dim: reduction order changes, value does not."""
|
|
|
|
|
|
|
| 971 |
def shuffle(args, seed):
|
| 972 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 973 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
|
|
| 977 |
|
| 978 |
@staticmethod
|
| 979 |
def matmul_k():
|
| 980 |
+
"""Consistent K permutation of (A [.., M, K], B [.., K, N]): product unchanged, accumulation order not."""
|
|
|
|
| 981 |
def shuffle(args, seed):
|
| 982 |
a_m, b_m = args[0], args[1]
|
| 983 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
|
|
| 991 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 992 |
|
| 993 |
|
| 994 |
+
def _compare_hints(trial):
|
| 995 |
+
hints = []
|
| 996 |
+
if trial.get("bias_one_sided_frac", 0) >= 0.9 and \
|
| 997 |
+
trial.get("frac_above_band", 0) > 0.5:
|
| 998 |
+
hints.append("systematic one-sided bias: check scale factors or "
|
| 999 |
+
"the accumulator dtype")
|
| 1000 |
+
if trial["nan_cand"] > trial["nan_ref"]:
|
| 1001 |
+
hints.append("candidate introduces NaN: check uninitialized "
|
| 1002 |
+
"memory or masking")
|
| 1003 |
+
nd = trial.get("first_divergent_nd")
|
| 1004 |
+
if nd and nd[-1] > 0 and any(nd[-1] % t == 0 for t in (16, 32, 64,
|
| 1005 |
+
128)):
|
| 1006 |
+
hints.append("divergence starts on a tile-aligned index: check "
|
| 1007 |
+
"boundary indexing")
|
| 1008 |
+
return hints
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def compare(ref, cand, gen=None, trials=8, order_shuffle=None,
|
| 1012 |
+
order_trials=8, ref_variants=None, ulp_tol=None, atol=None,
|
| 1013 |
+
rtol=None, band_margin=4.0, zero_atol=None, worst_k=8, seed=0,
|
| 1014 |
+
inputs=None, frac_above_tol=0.5):
|
| 1015 |
+
"""Adversarial correctness comparison with pointwise order bands, ULP evidence, and a one-sided-bias gate; the banded route cannot reject errors under band_margin*(band+2) ULP at order-stable elements."""
|
| 1016 |
+
if (gen is None) == (inputs is None):
|
| 1017 |
+
raise ValueError("pass exactly one of gen (a seed -> args "
|
| 1018 |
+
"callable) or inputs (a tuple of concrete "
|
| 1019 |
+
"tensors)")
|
| 1020 |
+
if inputs is not None:
|
| 1021 |
+
if isinstance(inputs, torch.Tensor):
|
| 1022 |
+
inputs = (inputs,)
|
| 1023 |
+
if not all(isinstance(x, torch.Tensor) for x in inputs):
|
| 1024 |
+
raise ValueError("inputs must be tensors; wrap scalars with "
|
| 1025 |
+
"torch.tensor first")
|
| 1026 |
+
fixed = tuple(inputs)
|
| 1027 |
+
gen = lambda seed: fixed # noqa: E731
|
| 1028 |
+
trials = 1
|
| 1029 |
worst = {"max_ulp": -1}
|
| 1030 |
total_hist = {}
|
| 1031 |
per_trial = []
|
| 1032 |
max_abs_seen = 0.0
|
| 1033 |
max_rel_seen = 0.0
|
| 1034 |
+
out_shapes = []
|
| 1035 |
for t in range(trials):
|
| 1036 |
args = gen(seed + t)
|
| 1037 |
+
if not isinstance(args, (tuple, list)):
|
| 1038 |
+
raise ValueError("gen(seed) must return a tuple of args; got "
|
| 1039 |
+
f"{type(args).__name__}; return (x,) for a "
|
| 1040 |
+
"single-argument op")
|
| 1041 |
r_outs = _as_outputs(ref(*args))
|
| 1042 |
c_outs = _as_outputs(cand(*args))
|
| 1043 |
+
if len(r_outs) != len(c_outs):
|
| 1044 |
+
raise ValueError(f"reference returned {len(r_outs)} outputs, "
|
| 1045 |
+
f"candidate {len(c_outs)}; make the arities "
|
| 1046 |
+
"match")
|
| 1047 |
+
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
| 1048 |
+
if r.dtype != c.dtype:
|
| 1049 |
+
raise ValueError(f"output {oi} dtype mismatch: ref "
|
| 1050 |
+
f"{r.dtype}, cand {c.dtype}; cast the "
|
| 1051 |
+
"candidate to the reference dtype")
|
| 1052 |
+
if r.shape != c.shape:
|
| 1053 |
+
raise ValueError(f"output {oi} shape mismatch: ref "
|
| 1054 |
+
f"{tuple(r.shape)}, cand "
|
| 1055 |
+
f"{tuple(c.shape)}")
|
| 1056 |
+
out_shapes = [tuple(r.shape) for r in r_outs]
|
| 1057 |
+
# pointwise band: each element judged against its own measured order sensitivity
|
| 1058 |
bands = None
|
| 1059 |
band = 0
|
| 1060 |
band_score = 0.0
|
|
|
|
| 1078 |
max_ulp = 0
|
| 1079 |
mean_ulp = 0.0
|
| 1080 |
first_div = -1
|
| 1081 |
+
first_div_nd = None
|
| 1082 |
n_el = 0
|
| 1083 |
n_above_band = 0
|
| 1084 |
+
n_diverging = 0
|
| 1085 |
+
n_cand_high = 0
|
| 1086 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 1087 |
worst_elements = []
|
| 1088 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
|
|
| 1100 |
mean_ulp += float(d.double().sum().item())
|
| 1101 |
n_el += d.numel()
|
| 1102 |
nz = (d > 0)
|
| 1103 |
+
n_diverging += int(nz.sum().item())
|
| 1104 |
+
n_cand_high += int(((c > r) & nz).sum().item())
|
| 1105 |
if first_div < 0 and nz.any():
|
| 1106 |
first_div = int(nz.flatten().nonzero()[0].item())
|
| 1107 |
+
first_div_nd = _unravel(first_div, r.shape)
|
| 1108 |
if nz.any():
|
| 1109 |
buckets = torch.clamp(
|
| 1110 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
|
|
| 1121 |
if val > 0:
|
| 1122 |
worst_elements.append({
|
| 1123 |
"output": oi, "index": int(ix),
|
| 1124 |
+
"index_nd": _unravel(int(ix), r.shape),
|
| 1125 |
"ref": float(rf[ix].item()),
|
| 1126 |
"cand": float(cf[ix].item()),
|
| 1127 |
"ulp": int(val)})
|
|
|
|
| 1141 |
inf_r += int(torch.isinf(r).sum().item())
|
| 1142 |
inf_c += int(torch.isinf(c).sum().item())
|
| 1143 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
| 1144 |
+
frac_pos = n_cand_high / max(n_diverging, 1)
|
| 1145 |
trial_rep = {
|
| 1146 |
"max_ulp": max_ulp,
|
| 1147 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 1148 |
"first_divergent_index": first_div,
|
| 1149 |
+
"first_divergent_nd": first_div_nd,
|
| 1150 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 1151 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 1152 |
"order_band_ulp": band,
|
| 1153 |
"band_score": round(band_score, 2),
|
| 1154 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
| 1155 |
+
"bias_one_sided_frac": round(max(frac_pos, 1 - frac_pos), 4)
|
| 1156 |
+
if n_diverging else 0.0,
|
| 1157 |
"worst_elements": worst_elements[:worst_k],
|
| 1158 |
}
|
| 1159 |
per_trial.append(trial_rep)
|
| 1160 |
if max_ulp > worst["max_ulp"]:
|
| 1161 |
worst = trial_rep
|
|
|
|
| 1162 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 1163 |
+
banded = order_shuffle is not None or ref_variants
|
| 1164 |
+
if banded:
|
| 1165 |
score = max(t["band_score"] for t in per_trial)
|
| 1166 |
ulp_ok = score <= band_margin
|
| 1167 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
|
|
| 1176 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 1177 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 1178 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 1179 |
+
# one-sided bias gate: a uniform small error is above-band nearly everywhere in one direction
|
| 1180 |
+
bias_fail = False
|
| 1181 |
+
if banded and (ulp_ok or tol_ok):
|
| 1182 |
+
w_frac = max(t["frac_above_band"] for t in per_trial)
|
| 1183 |
+
w_side = max(t["bias_one_sided_frac"] for t in per_trial)
|
| 1184 |
+
if w_frac > frac_above_tol and w_side >= 0.9:
|
| 1185 |
+
bias_fail = True
|
| 1186 |
+
criterion += ("; FAILED bias gate: frac_above_band %.3f > %s "
|
| 1187 |
+
"with one-sided fraction %.2f" %
|
| 1188 |
+
(w_frac, frac_above_tol, w_side))
|
| 1189 |
+
passed = bool((ulp_ok or tol_ok) and not bias_fail)
|
| 1190 |
+
hints = [] if passed else _compare_hints(worst)
|
| 1191 |
+
if passed:
|
| 1192 |
+
verdict = "pass: " + (
|
| 1193 |
+
f"max {max_seen} ULP within the order band" if banded
|
| 1194 |
+
else f"max {max_seen} ULP")
|
| 1195 |
+
elif bias_fail:
|
| 1196 |
+
verdict = ("fail: uniform bias, %.0f%% of elements above their "
|
| 1197 |
+
"order band in one direction" %
|
| 1198 |
+
(100 * max(t["frac_above_band"] for t in per_trial)))
|
| 1199 |
+
else:
|
| 1200 |
+
w0 = worst["worst_elements"][0] if worst["worst_elements"] else None
|
| 1201 |
+
verdict = "fail: max %d ULP" % max_seen + (
|
| 1202 |
+
" at index %s (ref %.6g, cand %.6g)" %
|
| 1203 |
+
(w0["index_nd"], w0["ref"], w0["cand"]) if w0 else "")
|
| 1204 |
+
rep = {
|
| 1205 |
+
"pass": passed,
|
| 1206 |
+
"verdict": verdict,
|
| 1207 |
"criterion": criterion,
|
| 1208 |
"worst_trial": worst,
|
| 1209 |
"max_abs": max_abs_seen,
|
|
|
|
| 1211 |
"trials": per_trial,
|
| 1212 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 1213 |
}
|
| 1214 |
+
if hints:
|
| 1215 |
+
rep["hints"] = hints
|
| 1216 |
+
return rep
|
| 1217 |
|
| 1218 |
|
| 1219 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
|
|
| 1229 |
return False
|
| 1230 |
|
| 1231 |
|
| 1232 |
+
class _RandnMaker:
|
| 1233 |
+
"""Picklable default maker: `arity` same-shape randn tensors (zeros for empty shapes)."""
|
| 1234 |
+
|
| 1235 |
+
def __init__(self, arity=1):
|
| 1236 |
+
self.arity = arity
|
| 1237 |
+
|
| 1238 |
+
def __call__(self, shape, dtype=torch.float32):
|
| 1239 |
+
n = math.prod(shape)
|
| 1240 |
+
if n:
|
| 1241 |
+
out = tuple(torch.randn(tuple(shape), device="cuda",
|
| 1242 |
+
dtype=dtype)
|
| 1243 |
+
for _ in range(self.arity))
|
| 1244 |
+
else:
|
| 1245 |
+
out = tuple(torch.zeros(tuple(shape), device="cuda",
|
| 1246 |
+
dtype=dtype)
|
| 1247 |
+
for _ in range(self.arity))
|
| 1248 |
+
return out if self.arity > 1 else out[0]
|
| 1249 |
+
|
| 1250 |
+
|
| 1251 |
+
def _default_make(arity=1):
|
| 1252 |
+
"""Default maker instance; a class so isolate=True can transport it."""
|
| 1253 |
+
return _RandnMaker(arity)
|
| 1254 |
+
|
| 1255 |
+
|
| 1256 |
def _fuzz_build(make, shape, dtype):
|
| 1257 |
try:
|
| 1258 |
out = make(tuple(shape), dtype)
|
|
|
|
| 1269 |
if v == "strided_rows":
|
| 1270 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 1271 |
if v == "offset_slice":
|
| 1272 |
+
# storage-offset-1 view: misaligned for vectorized loads
|
| 1273 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 1274 |
if v == "overflow_expand":
|
| 1275 |
if x.numel() == 0:
|
|
|
|
| 1283 |
|
| 1284 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 1285 |
stop_on_context_loss=True):
|
| 1286 |
+
"""All variant cases for one (shape, dtype) group; returns (rows, context_lost)."""
|
|
|
|
| 1287 |
rows = []
|
| 1288 |
bases = _fuzz_build(make, shape, dtype)
|
| 1289 |
lost = False
|
|
|
|
| 1320 |
got = op(*xs)
|
| 1321 |
want = ref(*xs)
|
| 1322 |
if v == "overflow_expand":
|
| 1323 |
+
# bounded comparison: head plus the high-index tail where int32 indexing wraps
|
|
|
|
| 1324 |
w = 1 << 20
|
| 1325 |
gf, wf = got.flatten(), want.flatten()
|
| 1326 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
|
|
| 1345 |
return rows, lost
|
| 1346 |
|
| 1347 |
|
| 1348 |
+
def _fn_spec(fn, name):
|
| 1349 |
+
"""Picklable transport spec for a callable, with a source fallback for local defs."""
|
| 1350 |
+
import pickle
|
| 1351 |
+
try:
|
| 1352 |
+
return ("pickle", pickle.dumps(fn), None)
|
| 1353 |
+
except Exception: # noqa: BLE001
|
| 1354 |
+
import inspect
|
| 1355 |
+
import textwrap
|
| 1356 |
+
try:
|
| 1357 |
+
src = textwrap.dedent(inspect.getsource(fn))
|
| 1358 |
+
except (OSError, TypeError):
|
| 1359 |
+
src = ""
|
| 1360 |
+
if not src.startswith("def "):
|
| 1361 |
+
raise ValueError(
|
| 1362 |
+
f"{name} is not picklable and not a plain def; define "
|
| 1363 |
+
"op/ref/make as top-level functions (not lambdas or "
|
| 1364 |
+
"closures) to use isolate=True") from None
|
| 1365 |
+
return ("source", src, fn.__name__)
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
_POOL_BOOTSTRAP = r"""
|
| 1369 |
+
import json, os, pickle, sys
|
| 1370 |
meta = json.load(open(sys.argv[1], "r"))
|
| 1371 |
+
sys.path[:0] = meta["sys_path"]
|
| 1372 |
try:
|
| 1373 |
import kernels
|
| 1374 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
|
|
| 1381 |
import load_local
|
| 1382 |
_mk = load_local.load()
|
| 1383 |
import torch
|
| 1384 |
+
_ns = {"torch": torch}
|
| 1385 |
+
def _resolve(spec):
|
| 1386 |
+
kind, payload, name = spec
|
| 1387 |
+
if kind == "pickle":
|
| 1388 |
+
return pickle.loads(payload)
|
| 1389 |
+
exec(payload, _ns)
|
| 1390 |
+
return _ns[name]
|
| 1391 |
+
_op, _ref, _make = (_resolve(s) for s in
|
| 1392 |
+
pickle.load(open(meta["fns"], "rb")))
|
| 1393 |
+
print("ready", flush=True)
|
| 1394 |
+
for line in sys.stdin:
|
| 1395 |
+
job = json.loads(line)
|
| 1396 |
+
rows, _l = _mk._fuzz_group(
|
| 1397 |
+
_op, _ref, _make, tuple(job["shape"]),
|
| 1398 |
+
getattr(torch, job["dtype"]), tuple(job["variants"]),
|
| 1399 |
+
job["ulp_tol"], job["device"], stop_on_context_loss=False)
|
| 1400 |
+
pickle.dump(rows, open(job["out"], "wb"))
|
| 1401 |
+
print("done", flush=True)
|
| 1402 |
"""
|
| 1403 |
|
| 1404 |
|
| 1405 |
+
class _FuzzPool:
|
| 1406 |
+
"""One persistent isolation worker fed groups over stdin, respawned only after a crash."""
|
| 1407 |
+
|
| 1408 |
+
def __init__(self, meta_path, stderr_path, timeout_s):
|
| 1409 |
+
self.meta_path = meta_path
|
| 1410 |
+
self.stderr_path = stderr_path
|
| 1411 |
+
self.timeout_s = timeout_s
|
| 1412 |
+
self.proc = None
|
| 1413 |
+
self.q = None
|
| 1414 |
+
|
| 1415 |
+
def _spawn(self):
|
| 1416 |
+
import queue
|
| 1417 |
+
import subprocess
|
| 1418 |
+
import sys as _sys
|
| 1419 |
+
import threading
|
| 1420 |
+
self.errf = open(self.stderr_path, "ab")
|
| 1421 |
+
self.proc = subprocess.Popen(
|
| 1422 |
+
[_sys.executable, "-c", _POOL_BOOTSTRAP, self.meta_path],
|
| 1423 |
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
| 1424 |
+
stderr=self.errf, text=True)
|
| 1425 |
+
self.q = queue.Queue()
|
| 1426 |
+
|
| 1427 |
+
def reader(p, q):
|
| 1428 |
+
for line in p.stdout:
|
| 1429 |
+
q.put(line.strip())
|
| 1430 |
+
threading.Thread(target=reader, args=(self.proc, self.q),
|
| 1431 |
+
daemon=True).start()
|
| 1432 |
+
try:
|
| 1433 |
+
first = self.q.get(timeout=self.timeout_s)
|
| 1434 |
+
except Exception: # noqa: BLE001
|
| 1435 |
+
first = None
|
| 1436 |
+
return first == "ready"
|
| 1437 |
+
|
| 1438 |
+
def run(self, job_line):
|
| 1439 |
+
"""Returns True when the worker reports done, False on crash or timeout."""
|
| 1440 |
+
import queue
|
| 1441 |
+
if self.proc is None or self.proc.poll() is not None:
|
| 1442 |
+
if not self._spawn():
|
| 1443 |
+
return False
|
| 1444 |
+
try:
|
| 1445 |
+
self.proc.stdin.write(job_line + "\n")
|
| 1446 |
+
self.proc.stdin.flush()
|
| 1447 |
+
except OSError:
|
| 1448 |
+
return False
|
| 1449 |
+
try:
|
| 1450 |
+
return self.q.get(timeout=self.timeout_s) == "done"
|
| 1451 |
+
except queue.Empty:
|
| 1452 |
+
if self.proc.poll() is None:
|
| 1453 |
+
self.proc.kill()
|
| 1454 |
+
return False
|
| 1455 |
+
|
| 1456 |
+
def stderr_tail(self):
|
| 1457 |
+
try:
|
| 1458 |
+
self.errf.flush()
|
| 1459 |
+
with open(self.stderr_path, "rb") as f:
|
| 1460 |
+
return f.read()[-400:].decode(errors="replace")
|
| 1461 |
+
except OSError:
|
| 1462 |
+
return ""
|
| 1463 |
+
|
| 1464 |
+
def close(self):
|
| 1465 |
+
if self.proc is not None and self.proc.poll() is None:
|
| 1466 |
+
self.proc.kill()
|
| 1467 |
+
try:
|
| 1468 |
+
self.errf.close()
|
| 1469 |
+
except Exception: # noqa: BLE001
|
| 1470 |
+
pass
|
| 1471 |
+
|
| 1472 |
+
|
| 1473 |
+
def _fuzz_hints(rows):
|
| 1474 |
+
hints = []
|
| 1475 |
+
bad = [c for c in rows if c["status"] == "mismatch"]
|
| 1476 |
+
if bad and all(c["variant"] in ("transposed", "strided_rows",
|
| 1477 |
+
"offset_slice") for c in bad):
|
| 1478 |
+
hints.append("mismatches only on non-contiguous variants: the op "
|
| 1479 |
+
"assumes contiguity; call .contiguous() or honor "
|
| 1480 |
+
"strides")
|
| 1481 |
+
if any(c["variant"] == "overflow_expand" and
|
| 1482 |
+
c["status"] == "mismatch" for c in rows):
|
| 1483 |
+
hints.append("overflow_expand mismatch: int32 indexing wraps past "
|
| 1484 |
+
"2^31 elements; use 64-bit index arithmetic")
|
| 1485 |
+
return hints
|
| 1486 |
+
|
| 1487 |
+
|
| 1488 |
+
def fuzz_shapes(op, ref, make=None, shapes=None, variants=_FUZZ_VARIANTS,
|
| 1489 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 1490 |
+
isolate_timeout_s=180, device="cuda", arity=1):
|
| 1491 |
+
"""Edge shapes, dtypes, and layout mutations with per-case repro recipes; isolate=True survives context-killing candidates via a pooled worker."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1492 |
if shapes is None:
|
| 1493 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1494 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
| 1495 |
+
if make is None:
|
| 1496 |
+
make = _default_make(arity)
|
| 1497 |
|
| 1498 |
rows = []
|
| 1499 |
lost = False
|
| 1500 |
if isolate:
|
| 1501 |
import json
|
| 1502 |
import pickle
|
|
|
|
| 1503 |
import sys as _sys
|
| 1504 |
import tempfile
|
| 1505 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1506 |
fns = os.path.join(tmpdir, "fns.pkl")
|
| 1507 |
+
specs = [_fn_spec(f, n) for f, n in ((op, "op"), (ref, "ref"),
|
| 1508 |
+
(make, "make"))]
|
| 1509 |
with open(fns, "wb") as f:
|
| 1510 |
+
pickle.dump(specs, f)
|
| 1511 |
+
meta_path = os.path.join(tmpdir, "meta.json")
|
| 1512 |
+
with open(meta_path, "w", encoding="utf-8") as f:
|
| 1513 |
+
json.dump({"sys_path": [p for p in _sys.path
|
| 1514 |
+
if isinstance(p, str)],
|
| 1515 |
+
"repo": _REPO_ID, "version": _REPO_VERSION,
|
| 1516 |
+
"fns": fns}, f)
|
| 1517 |
+
pool = _FuzzPool(meta_path, os.path.join(tmpdir, "stderr.log"),
|
| 1518 |
+
isolate_timeout_s)
|
| 1519 |
+
try:
|
| 1520 |
+
for dtype in dtypes:
|
| 1521 |
+
for shape in shapes:
|
| 1522 |
+
out = os.path.join(tmpdir, "rows.pkl")
|
| 1523 |
+
if os.path.exists(out):
|
| 1524 |
+
os.remove(out)
|
| 1525 |
+
job = json.dumps({
|
| 1526 |
"shape": list(shape),
|
| 1527 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1528 |
"variants": list(variants),
|
| 1529 |
"ulp_tol": ulp_tol, "device": device,
|
| 1530 |
+
"out": out})
|
| 1531 |
+
ok = pool.run(job)
|
| 1532 |
+
if ok and os.path.exists(out):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
with open(out, "rb") as f:
|
| 1534 |
+
rows.extend(pickle.load(f))
|
| 1535 |
+
else:
|
| 1536 |
+
rows.append({
|
| 1537 |
+
"shape": tuple(shape), "variant": "*",
|
| 1538 |
+
"dtype": str(dtype).replace("torch.", ""),
|
| 1539 |
+
"status": "crashed_isolated",
|
| 1540 |
+
"stderr_tail": pool.stderr_tail()})
|
| 1541 |
+
finally:
|
| 1542 |
+
pool.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1543 |
else:
|
| 1544 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1545 |
for gi, (dtype, shape) in enumerate(pending):
|
|
|
|
| 1556 |
n_bad = sum(1 for r in rows
|
| 1557 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1558 |
"skipped_empty"))
|
| 1559 |
+
rep = {"cases": rows, "failed": n_bad, "total": len(rows),
|
| 1560 |
+
"context_lost": lost}
|
| 1561 |
+
hints = _fuzz_hints(rows)
|
| 1562 |
+
if hints:
|
| 1563 |
+
rep["hints"] = hints
|
| 1564 |
+
if n_bad == 0:
|
| 1565 |
+
rep["verdict"] = "pass: %d/%d cases ok" % (len(rows) - n_bad,
|
| 1566 |
+
len(rows))
|
| 1567 |
+
else:
|
| 1568 |
+
worst_case = next(c for c in rows
|
| 1569 |
+
if c["status"] not in ("ok", "skipped_vram",
|
| 1570 |
+
"skipped_empty"))
|
| 1571 |
+
rep["verdict"] = "fail: %d/%d cases (%s at %s %s)" % (
|
| 1572 |
+
n_bad, len(rows), worst_case["status"], worst_case["shape"],
|
| 1573 |
+
worst_case["variant"])
|
| 1574 |
+
return rep
|
| 1575 |
|
| 1576 |
|
| 1577 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1578 |
check=None, budget_s=None, race=False):
|
| 1579 |
+
"""Launch-configuration search, fastest first, with a correctness gate per row and no silently dropped points."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1580 |
keys = list(grid.keys())
|
| 1581 |
|
| 1582 |
def expand(i, acc):
|
|
|
|
| 1641 |
continue
|
| 1642 |
rows.append(run_point(p, iters))
|
| 1643 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
| 1644 |
+
if rows:
|
| 1645 |
+
n_pruned = sum(1 for r in rows if r.get("status") == "pruned_race")
|
| 1646 |
+
n_skip = sum(1 for r in rows if r.get("status") == "not_run")
|
| 1647 |
+
best = rows[0]
|
| 1648 |
+
rows_verdict = "fastest %s at %.4g ms" % (
|
| 1649 |
+
{k: best[k] for k in keys}, best["median_ms"])
|
| 1650 |
+
if n_pruned:
|
| 1651 |
+
rows_verdict += "; %d pruned" % n_pruned
|
| 1652 |
+
if n_skip:
|
| 1653 |
+
rows_verdict += "; %d not run" % n_skip
|
| 1654 |
+
best["verdict"] = rows_verdict
|
| 1655 |
return rows
|
| 1656 |
|
| 1657 |
|
| 1658 |
+
def judge(cand, ref, inputs=None, gen=None, bytes=None, flops=None,
|
| 1659 |
+
dtype=None, order_shuffle=None, make=None, quick_fuzz=True,
|
| 1660 |
+
device="cuda"):
|
| 1661 |
+
"""One-call verdict: compare, a quick fuzz, and a roofline bench against the cached dossier."""
|
| 1662 |
+
if (gen is None) == (inputs is None):
|
| 1663 |
+
raise ValueError("pass exactly one of inputs (concrete tensors) "
|
| 1664 |
+
"or gen (seed -> args)")
|
| 1665 |
+
if inputs is not None and isinstance(inputs, torch.Tensor):
|
| 1666 |
+
inputs = (inputs,)
|
| 1667 |
+
args0 = tuple(inputs) if inputs is not None else gen(0)
|
| 1668 |
+
arity = len(args0)
|
| 1669 |
+
if dtype is None:
|
| 1670 |
+
dt0 = args0[0].dtype
|
| 1671 |
+
dtype = {torch.float16: "fp16", torch.bfloat16: "bf16"}.get(
|
| 1672 |
+
dt0, "f32")
|
| 1673 |
+
dossier = probe_device(device, quick=True, cached=True,
|
| 1674 |
+
tenancy_check=False)
|
| 1675 |
+
cmp_rep = compare(ref, cand, gen=gen, inputs=inputs, trials=4,
|
| 1676 |
+
order_shuffle=order_shuffle)
|
| 1677 |
+
fuzz_rep = None
|
| 1678 |
+
if quick_fuzz:
|
| 1679 |
+
fuzz_rep = fuzz_shapes(
|
| 1680 |
+
cand, ref, make=make, arity=arity,
|
| 1681 |
+
shapes=[(1, 17), (3, 63), (128, 128)],
|
| 1682 |
+
variants=("contig", "transposed", "offset_slice"),
|
| 1683 |
+
dtypes=(args0[0].dtype,))
|
| 1684 |
+
bench_rep = None
|
| 1685 |
+
if inputs is not None:
|
| 1686 |
+
bench_rep = bench(lambda: cand(*inputs), iters=32, warmup=6,
|
| 1687 |
+
bytes=bytes, flops=flops, dossier=dossier,
|
| 1688 |
+
dtype=dtype, device=device)
|
| 1689 |
+
passed = cmp_rep["pass"] and (fuzz_rep is None or
|
| 1690 |
+
fuzz_rep["failed"] == 0)
|
| 1691 |
+
parts = [cmp_rep["verdict"]]
|
| 1692 |
+
if fuzz_rep is not None:
|
| 1693 |
+
parts.append(fuzz_rep["verdict"])
|
| 1694 |
+
if bench_rep is not None:
|
| 1695 |
+
parts.append(bench_rep["verdict"])
|
| 1696 |
+
rep = {"pass": passed, "verdict" : " | ".join(parts),
|
| 1697 |
+
"compare": cmp_rep, "fuzz": fuzz_rep, "bench": bench_rep,
|
| 1698 |
+
"device": dossier["device"]["name"]}
|
| 1699 |
+
hints = list(cmp_rep.get("hints", []))
|
| 1700 |
+
if fuzz_rep is not None:
|
| 1701 |
+
hints += fuzz_rep.get("hints", [])
|
| 1702 |
+
if hints:
|
| 1703 |
+
rep["hints"] = hints
|
| 1704 |
+
return rep
|
| 1705 |
+
|
| 1706 |
+
|
| 1707 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1708 |
+
"""Device buffer for MK_STAMP (1-D) or MK_STAMP_BLOCK ([blocks, n]) stamps."""
|
|
|
|
|
|
|
| 1709 |
if blocks is not None:
|
| 1710 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1711 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
|
|
| 1715 |
|
| 1716 |
|
| 1717 |
def pinned_dossiers():
|
| 1718 |
+
"""Names of the release-pinned dossiers, one per certified architecture."""
|
|
|
|
| 1719 |
return sorted(_PINNED.keys())
|
| 1720 |
|
| 1721 |
|
|
|
|
| 1734 |
|
| 1735 |
|
| 1736 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1737 |
+
"""Per-leaf relative drift of a fresh dossier against the release pin for the same architecture."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1738 |
if pinned is None:
|
| 1739 |
arch = current["device"]["sm_arch"]
|
| 1740 |
matches = [k for k, v in _PINNED.items()
|
|
|
|
| 1757 |
"rel": round(rel, 4)})
|
| 1758 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1759 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1760 |
+
rep = {"entries": entries, "worst_rel": worst, "over_tol": over,
|
| 1761 |
+
"rel_tol": rel_tol, "compared": len(entries),
|
| 1762 |
+
"pass": len(over) == 0}
|
| 1763 |
+
rep["verdict"] = ("pass: worst drift %.3f over %d entries" %
|
| 1764 |
+
(worst, len(entries))) if rep["pass"] else \
|
| 1765 |
+
("fail: %d entries over %.2f, worst %s at %.3f" %
|
| 1766 |
+
(len(over), rel_tol, over[0]["path"], worst))
|
| 1767 |
+
return rep
|
| 1768 |
|
| 1769 |
|
| 1770 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1771 |
device="cuda"):
|
| 1772 |
+
"""Run the shipped MK_STAMP_BLOCK example (load, compute, drained store) and decode its stamps."""
|
|
|
|
|
|
|
| 1773 |
props = torch.cuda.get_device_properties(device)
|
| 1774 |
if blocks is None:
|
| 1775 |
blocks = props.multi_processor_count
|
|
|
|
| 1782 |
|
| 1783 |
|
| 1784 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1785 |
+
"""Decode MK_STAMP deltas to ticks and microseconds; 2-D buffers yield min/median/max across blocks."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1786 |
if clock_ghz is None:
|
| 1787 |
clock_ghz = measure_clock(stamps.device)
|
| 1788 |
|
|
|
|
| 1818 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1819 |
"blocks_reporting": int(live.sum().item()),
|
| 1820 |
"blocks_total": int(live.numel())}
|
| 1821 |
+
|
| 1822 |
+
|
| 1823 |
+
_SCHEMAS = {
|
| 1824 |
+
"probe_device": {
|
| 1825 |
+
"device": "name, sm_arch, sms, l2_bytes, vram_bytes, torch, "
|
| 1826 |
+
"cuda_runtime, ecc, driver, power_limit_w",
|
| 1827 |
+
"clock": "max/min/final GHz under sustained FMA load plus sag_pct",
|
| 1828 |
+
"dram_bw": "GB/s: triad_4B/8B/16B, read_only, write_only, "
|
| 1829 |
+
"random_gather_16B",
|
| 1830 |
+
"l2_bw": "GB/s over an L2-resident working set",
|
| 1831 |
+
"latency": "cycles: smem, l1, l2, dram (min of 3 chases) plus "
|
| 1832 |
+
"ns_per_cycle",
|
| 1833 |
+
"fma_f32 / fma_f64": "TFLOP/s of the register-resident pipes",
|
| 1834 |
+
"fma_ilp": "TFLOP/s at 1/2/4 chains plus ratio_4_1; near 4 means "
|
| 1835 |
+
"latency-bound",
|
| 1836 |
+
"mma": "TFLOP/s per dtype; sparse carries executed and "
|
| 1837 |
+
"dense_equivalent; verified_k_lanes carries "
|
| 1838 |
+
"executed_k_per_iter, vs_nominal, calibration_linear; "
|
| 1839 |
+
"fp8_accum carries the accumulate-precision stall",
|
| 1840 |
+
"mma_fed": "TFLOP/s of smem-staged fp16 tiles (staging exposed)",
|
| 1841 |
+
"smem_bw": "GB/s aggregate, conflict-light pattern",
|
| 1842 |
+
"smem_conflict_penalty": "time ratio vs stride 1 per lane stride",
|
| 1843 |
+
"transfer_bw": "GB/s: h2d/d2h pinned and pageable, d2d",
|
| 1844 |
+
"atomics_f32": "Gop/s curve over distinct addresses plus "
|
| 1845 |
+
"shared_same_addr",
|
| 1846 |
+
"launch_overhead": "us: torch_queued, torch_round_trip, "
|
| 1847 |
+
"raw_queued, raw_round_trip",
|
| 1848 |
+
"grid_barrier": "us per grid.sync or 'unsupported'",
|
| 1849 |
+
"occupancy_blocks_per_sm": "API table by (threads, dyn smem)",
|
| 1850 |
+
"occupancy_knees_fma_tflops": "timed dependent-chain table",
|
| 1851 |
+
},
|
| 1852 |
+
"bench": {
|
| 1853 |
+
"median_ms": "median over accepted samples",
|
| 1854 |
+
"sustained_ms": "median over all samples",
|
| 1855 |
+
"median_ci95_ms / p05_ms / p95_ms / min_ms / mean_ms":
|
| 1856 |
+
"distribution of accepted samples",
|
| 1857 |
+
"samples / rejected_throttled / rejection_basis":
|
| 1858 |
+
"counts and whether rejection used sm_clock or "
|
| 1859 |
+
"bandwidth_consistency",
|
| 1860 |
+
"no_clean_window": "present when every window ran throttled; the "
|
| 1861 |
+
"median is then a throttled-regime number",
|
| 1862 |
+
"clock_ghz": "best and worst SM clock probes around the run",
|
| 1863 |
+
"achieved_gbs / achieved_tflops / arithmetic_intensity":
|
| 1864 |
+
"rates from declared traffic",
|
| 1865 |
+
"roofline": "bound, ridge_flops_per_byte, pct_of_roof, peak_dtype",
|
| 1866 |
+
"graph_captured / graph_fallback_reason": "CUDA-graph state",
|
| 1867 |
+
"alloc_growth_bytes_per_call": "present when fn leaks allocations",
|
| 1868 |
+
"host_overhead_suspected": "present when fn syncs or does host "
|
| 1869 |
+
"work inside; ops under ~50 us are "
|
| 1870 |
+
"below the detection floor",
|
| 1871 |
+
"verdict": "one-sentence summary",
|
| 1872 |
+
},
|
| 1873 |
+
"compare": {
|
| 1874 |
+
"pass": "final verdict incl. the one-sided bias gate",
|
| 1875 |
+
"verdict / criterion / hints": "summary, acceptance rule, and "
|
| 1876 |
+
"failure-pattern suggestions",
|
| 1877 |
+
"worst_trial": "max_ulp, mean_ulp, first_divergent_index(+_nd), "
|
| 1878 |
+
"NaN/Inf census, order_band_ulp, band_score, "
|
| 1879 |
+
"frac_above_band, bias_one_sided_frac, "
|
| 1880 |
+
"worst_elements (index, index_nd, ref, cand, ulp)",
|
| 1881 |
+
"max_abs / max_rel / ulp_hist_log2": "aggregate evidence",
|
| 1882 |
+
"floor": "the banded route cannot reject errors under "
|
| 1883 |
+
"band_margin*(band+2) ULP at order-stable elements",
|
| 1884 |
+
},
|
| 1885 |
+
"fuzz_shapes": {
|
| 1886 |
+
"cases": "per-case shape, variant, dtype, status, repro recipe",
|
| 1887 |
+
"status values": "ok | mismatch | exception | context_killed | "
|
| 1888 |
+
"context_lost | crashed_isolated | skipped_vram "
|
| 1889 |
+
"| skipped_empty",
|
| 1890 |
+
"failed / total / context_lost / verdict / hints": "aggregates",
|
| 1891 |
+
},
|
| 1892 |
+
"sweep": {
|
| 1893 |
+
"rows": "grid point plus median_ms, rejected_throttled, check?, "
|
| 1894 |
+
"achieved rates, status in pruned_race | not_run",
|
| 1895 |
+
"verdict": "on the fastest row",
|
| 1896 |
+
},
|
| 1897 |
+
"dossier_drift": {
|
| 1898 |
+
"entries": "path, current, pinned, rel per intersecting leaf",
|
| 1899 |
+
"worst_rel / over_tol / compared / pass / verdict": "aggregates",
|
| 1900 |
+
},
|
| 1901 |
+
"judge": {
|
| 1902 |
+
"pass / verdict / hints": "composite over compare, fuzz, bench",
|
| 1903 |
+
"compare / fuzz / bench": "the full sub-reports",
|
| 1904 |
+
},
|
| 1905 |
+
"read_stamps": {
|
| 1906 |
+
"phases": "ticks and us per phase; 2-D input adds min/median/max "
|
| 1907 |
+
"across blocks",
|
| 1908 |
+
"note": "stamps record issue; DRAIN variants include the store "
|
| 1909 |
+
"drain to the coherence point",
|
| 1910 |
+
},
|
| 1911 |
+
}
|
| 1912 |
+
|
| 1913 |
+
|
| 1914 |
+
def schema(name=None):
|
| 1915 |
+
"""Field layout of every report, so an agent can look up what a number means."""
|
| 1916 |
+
if name is None:
|
| 1917 |
+
return _SCHEMAS
|
| 1918 |
+
if name not in _SCHEMAS:
|
| 1919 |
+
raise KeyError(f"no schema for {name!r}; have "
|
| 1920 |
+
f"{sorted(_SCHEMAS)}")
|
| 1921 |
+
return _SCHEMAS[name]
|
build/{torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so → torch212-cxx11-cu130-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:43c0a08aedf84b5774061ee641a2244f265695210389af8a04402849511d99c2
|
| 3 |
+
size 1426264
|
build/torch212-cxx11-cu130-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _metakernel_cuda_0b9de42
|
| 3 |
+
ops = torch.ops._metakernel_cuda_0b9de42
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_metakernel_cuda_0b9de42::{op_name}"
|
build/torch212-cxx11-cu130-x86_64-linux/_pinned.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
Generated by dev/pin_dossier.py from full probe_device() runs on idle,
|
| 4 |
-
single-tenant cards; consumed by metakernel.dossier_drift.
|
| 5 |
-
"""
|
| 6 |
|
| 7 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 8 |
'confidence': 'high',
|
|
@@ -295,7 +291,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 295 |
'd2h_pinned': 10.8,
|
| 296 |
'h2d_pageable': 10.3,
|
| 297 |
'h2d_pinned': 11.8}}},
|
| 298 |
-
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.
|
| 299 |
'confidence': 'high',
|
| 300 |
'method': 'atomicAdd '
|
| 301 |
'hammer; curve '
|
|
@@ -308,21 +304,21 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 308 |
'contention) to '
|
| 309 |
'one per thread',
|
| 310 |
'units': 'Gop/s',
|
| 311 |
-
'value': {'curve_slots': {'1': 0.
|
| 312 |
-
'145408':
|
| 313 |
-
'4096':
|
| 314 |
-
'512':
|
| 315 |
-
'64': 13.
|
| 316 |
-
'8': 5.
|
| 317 |
-
'shared_same_addr': 12.
|
| 318 |
'clock': {'confidence': 'high',
|
| 319 |
-
'final_ghz': 2.
|
| 320 |
-
'max_ghz': 2.
|
| 321 |
'method': '24 back-to-back FMA '
|
| 322 |
'spin windows of 30M '
|
| 323 |
'ticks; wall/ticks '
|
| 324 |
'per window',
|
| 325 |
-
'min_ghz': 2.
|
| 326 |
'sag_pct': 0.3,
|
| 327 |
'units': 'GHz'},
|
| 328 |
'device': {'cuda_runtime': '12.6',
|
|
@@ -336,7 +332,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 336 |
'sms': 142,
|
| 337 |
'torch': '2.10.0+cu126',
|
| 338 |
'vram_bytes': 48305274880},
|
| 339 |
-
'dram_bw': {'clock_ghz': 2.
|
| 340 |
'confidence': 'high',
|
| 341 |
'method': 'streaming triad '
|
| 342 |
'c=a+s*b per access '
|
|
@@ -356,26 +352,41 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 356 |
'line-efficiency '
|
| 357 |
'claim',
|
| 358 |
'units': 'GB/s',
|
| 359 |
-
'value': {'random_gather_16B': 218.
|
| 360 |
-
'read_only':
|
| 361 |
-
'triad_16B':
|
| 362 |
-
'triad_4B':
|
| 363 |
-
'triad_8B':
|
| 364 |
'write_only': 766.5}},
|
| 365 |
-
'fma_f32': {'clock_ghz': 2.
|
| 366 |
'confidence': 'high',
|
| 367 |
'method': 'register-resident '
|
| 368 |
'FMA chains',
|
| 369 |
'units': 'TFLOP/s',
|
| 370 |
-
'value':
|
| 371 |
-
'fma_f64': {'clock_ghz': 2.
|
| 372 |
'confidence': 'high',
|
| 373 |
'method': 'register-resident '
|
| 374 |
'double FMA chains '
|
| 375 |
'(the DFMA pipe)',
|
| 376 |
'units': 'TFLOP/s',
|
| 377 |
-
'value': 1.
|
| 378 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
'confidence': 'high',
|
| 380 |
'method': 'cooperative '
|
| 381 |
'kernel, 142 '
|
|
@@ -383,8 +394,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 383 |
'threads',
|
| 384 |
'units': 'us per '
|
| 385 |
'grid.sync',
|
| 386 |
-
'value': 0.
|
| 387 |
-
'l2_bw': {'clock_ghz': 2.
|
| 388 |
'confidence': 'medium',
|
| 389 |
'method': 'in-kernel repeated '
|
| 390 |
'triad passes over 72 '
|
|
@@ -394,14 +405,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 394 |
'from HBM; amortized '
|
| 395 |
'over 64 passes',
|
| 396 |
'units': 'GB/s',
|
| 397 |
-
'value':
|
| 398 |
-
'latency': {'clock_ghz': 2.
|
| 399 |
'confidence': 'high',
|
| 400 |
'method': 'Sattolo-cycle '
|
| 401 |
-
'pointer chase
|
| 402 |
-
'
|
| 403 |
-
'
|
| 404 |
-
'ring
|
|
|
|
| 405 |
'self-evicting '
|
| 406 |
'chain (2x L2 in '
|
| 407 |
'touched lines) for '
|
|
@@ -413,40 +425,58 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 413 |
'footprints',
|
| 414 |
'units': 'cycles (single-lane '
|
| 415 |
'dependent loads)',
|
| 416 |
-
'value': {'dram_cycles':
|
| 417 |
'l1_cycles': 45.8,
|
| 418 |
-
'l2_cycles':
|
| 419 |
'ns_per_cycle': 0.372,
|
| 420 |
'smem_cycles': 30.0}},
|
| 421 |
-
'launch_overhead': {'clock_ghz': 2.
|
| 422 |
-
'confidence': '
|
| 423 |
'method': 'empty-kernel '
|
| 424 |
-
'trains
|
| 425 |
-
'
|
| 426 |
-
'launch
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
'units': 'us',
|
| 432 |
-
'value': {'
|
| 433 |
-
'
|
| 434 |
-
|
|
|
|
|
|
|
| 435 |
'confidence': 'medium',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
'method': 'register-fragment '
|
| 437 |
'mma_sync loops (wmma; '
|
| 438 |
'fp8 and 2:4-sparse '
|
| 439 |
'fp16 via inline PTX '
|
| 440 |
-
'm16n8k32;
|
| 441 |
-
'
|
| 442 |
-
'
|
| 443 |
-
'
|
| 444 |
-
'
|
| 445 |
-
'
|
| 446 |
-
'
|
| 447 |
-
'
|
| 448 |
-
'
|
| 449 |
-
'
|
| 450 |
'note': 'dense-tile rate without '
|
| 451 |
'memory traffic; real '
|
| 452 |
'kernels see this only '
|
|
@@ -454,25 +484,54 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 454 |
'behind it',
|
| 455 |
'units': 'TFLOP/s (TOP/s for '
|
| 456 |
'int8)',
|
| 457 |
-
'value': {'bf16':
|
| 458 |
-
'fp16':
|
| 459 |
-
'fp16_sparse_2_4':
|
| 460 |
-
|
| 461 |
-
'
|
| 462 |
-
'
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
| 464 |
'vs_nominal': 1.0},
|
| 465 |
-
'fp16': {'
|
|
|
|
| 466 |
'vs_nominal': 1.0},
|
| 467 |
-
'fp16_sparse_2_4': {'
|
|
|
|
| 468 |
'vs_nominal': 1.0},
|
| 469 |
-
'fp8_e4m3': {'
|
| 470 |
-
'
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
'vs_nominal': 1.0},
|
| 473 |
-
'tf32': {'
|
|
|
|
| 474 |
'vs_nominal': 1.0}}},
|
| 475 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
'confidence': 'high',
|
| 477 |
'method': 'occupancy '
|
| 478 |
'API '
|
|
@@ -501,7 +560,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 501 |
'16KB': 3,
|
| 502 |
'32KB': 3,
|
| 503 |
'48KB': 2}}},
|
| 504 |
-
'occupancy_knees_fma_tflops': {'clock_ghz': 2.
|
| 505 |
'confidence': 'high',
|
| 506 |
'method': 'a '
|
| 507 |
'single '
|
|
@@ -521,17 +580,6 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 521 |
'fixed '
|
| 522 |
'total '
|
| 523 |
'work; '
|
| 524 |
-
'throughput '
|
| 525 |
-
'is '
|
| 526 |
-
'proportional '
|
| 527 |
-
'to '
|
| 528 |
-
'resident '
|
| 529 |
-
'threads '
|
| 530 |
-
'until '
|
| 531 |
-
'the '
|
| 532 |
-
'pipe '
|
| 533 |
-
'saturates, '
|
| 534 |
-
'so '
|
| 535 |
'the '
|
| 536 |
'cliff '
|
| 537 |
'marks '
|
|
@@ -549,15 +597,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 549 |
'above',
|
| 550 |
'units': 'TFLOP/s',
|
| 551 |
'value': {'t1024': {'0KB': 75.5,
|
| 552 |
-
'32KB': 73.
|
| 553 |
-
'48KB':
|
| 554 |
-
't128': {'0KB': 79.
|
| 555 |
'32KB': 60.31,
|
| 556 |
-
'48KB':
|
| 557 |
-
't256': {'0KB':
|
| 558 |
-
'32KB':
|
| 559 |
-
'48KB':
|
| 560 |
-
'smem_bw': {'clock_ghz': 2.
|
| 561 |
'confidence': 'medium',
|
| 562 |
'method': 'block-resident '
|
| 563 |
'read+write '
|
|
@@ -566,12 +614,56 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 566 |
'aggregated across '
|
| 567 |
'SMs',
|
| 568 |
'note': 'bank-conflict-light '
|
| 569 |
-
'access pattern;
|
| 570 |
-
'
|
| 571 |
-
'
|
|
|
|
| 572 |
'units': 'GB/s',
|
| 573 |
-
'value':
|
| 574 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
'confidence': 'high',
|
| 576 |
'method': '256 MB torch '
|
| 577 |
'copies, '
|
|
@@ -583,8 +675,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 583 |
'direction of '
|
| 584 |
'traffic',
|
| 585 |
'units': 'GB/s',
|
| 586 |
-
'value': {'d2d':
|
| 587 |
-
'd2h_pageable':
|
| 588 |
-
'd2h_pinned': 26.
|
| 589 |
-
'h2d_pageable':
|
| 590 |
'h2d_pinned': 24.0}}}}
|
|
|
|
| 1 |
+
'''Release-pinned device dossiers, one per certified architecture; generated by dev/pin_dossier.py from idle single-tenant runs, consumed by dossier_drift.'''
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 4 |
'confidence': 'high',
|
|
|
|
| 291 |
'd2h_pinned': 10.8,
|
| 292 |
'h2d_pageable': 10.3,
|
| 293 |
'h2d_pinned': 11.8}}},
|
| 294 |
+
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.705,
|
| 295 |
'confidence': 'high',
|
| 296 |
'method': 'atomicAdd '
|
| 297 |
'hammer; curve '
|
|
|
|
| 304 |
'contention) to '
|
| 305 |
'one per thread',
|
| 306 |
'units': 'Gop/s',
|
| 307 |
+
'value': {'curve_slots': {'1': 0.71,
|
| 308 |
+
'145408': 413.99,
|
| 309 |
+
'4096': 184.9,
|
| 310 |
+
'512': 49.84,
|
| 311 |
+
'64': 13.25,
|
| 312 |
+
'8': 5.74},
|
| 313 |
+
'shared_same_addr': 12.23}},
|
| 314 |
'clock': {'confidence': 'high',
|
| 315 |
+
'final_ghz': 2.698,
|
| 316 |
+
'max_ghz': 2.705,
|
| 317 |
'method': '24 back-to-back FMA '
|
| 318 |
'spin windows of 30M '
|
| 319 |
'ticks; wall/ticks '
|
| 320 |
'per window',
|
| 321 |
+
'min_ghz': 2.5,
|
| 322 |
'sag_pct': 0.3,
|
| 323 |
'units': 'GHz'},
|
| 324 |
'device': {'cuda_runtime': '12.6',
|
|
|
|
| 332 |
'sms': 142,
|
| 333 |
'torch': '2.10.0+cu126',
|
| 334 |
'vram_bytes': 48305274880},
|
| 335 |
+
'dram_bw': {'clock_ghz': 2.705,
|
| 336 |
'confidence': 'high',
|
| 337 |
'method': 'streaming triad '
|
| 338 |
'c=a+s*b per access '
|
|
|
|
| 352 |
'line-efficiency '
|
| 353 |
'claim',
|
| 354 |
'units': 'GB/s',
|
| 355 |
+
'value': {'random_gather_16B': 218.1,
|
| 356 |
+
'read_only': 766.5,
|
| 357 |
+
'triad_16B': 682.1,
|
| 358 |
+
'triad_4B': 654.8,
|
| 359 |
+
'triad_8B': 670.9,
|
| 360 |
'write_only': 766.5}},
|
| 361 |
+
'fma_f32': {'clock_ghz': 2.705,
|
| 362 |
'confidence': 'high',
|
| 363 |
'method': 'register-resident '
|
| 364 |
'FMA chains',
|
| 365 |
'units': 'TFLOP/s',
|
| 366 |
+
'value': 86.58},
|
| 367 |
+
'fma_f64': {'clock_ghz': 2.705,
|
| 368 |
'confidence': 'high',
|
| 369 |
'method': 'register-resident '
|
| 370 |
'double FMA chains '
|
| 371 |
'(the DFMA pipe)',
|
| 372 |
'units': 'TFLOP/s',
|
| 373 |
+
'value': 1.257},
|
| 374 |
+
'fma_ilp': {'clock_ghz': 2.705,
|
| 375 |
+
'confidence': 'high',
|
| 376 |
+
'method': '1/2/4 independent '
|
| 377 |
+
'dependent FMA '
|
| 378 |
+
'chains per thread '
|
| 379 |
+
'at one block of 64 '
|
| 380 |
+
'threads per SM; a '
|
| 381 |
+
'ratio near 4 means '
|
| 382 |
+
'latency-bound at '
|
| 383 |
+
'that occupancy',
|
| 384 |
+
'units': 'TFLOP/s',
|
| 385 |
+
'value': {'ilp1': 11.75,
|
| 386 |
+
'ilp2': 23.37,
|
| 387 |
+
'ilp4': 43.03,
|
| 388 |
+
'ratio_4_1': 3.66}},
|
| 389 |
+
'grid_barrier': {'clock_ghz': 2.705,
|
| 390 |
'confidence': 'high',
|
| 391 |
'method': 'cooperative '
|
| 392 |
'kernel, 142 '
|
|
|
|
| 394 |
'threads',
|
| 395 |
'units': 'us per '
|
| 396 |
'grid.sync',
|
| 397 |
+
'value': 0.55},
|
| 398 |
+
'l2_bw': {'clock_ghz': 2.705,
|
| 399 |
'confidence': 'medium',
|
| 400 |
'method': 'in-kernel repeated '
|
| 401 |
'triad passes over 72 '
|
|
|
|
| 405 |
'from HBM; amortized '
|
| 406 |
'over 64 passes',
|
| 407 |
'units': 'GB/s',
|
| 408 |
+
'value': 9590.6},
|
| 409 |
+
'latency': {'clock_ghz': 2.691,
|
| 410 |
'confidence': 'high',
|
| 411 |
'method': 'Sattolo-cycle '
|
| 412 |
+
'pointer chase, min '
|
| 413 |
+
'of 3 runs; 16 KB '
|
| 414 |
+
'ring for L1, 2 MB '
|
| 415 |
+
'TLB-resident ring '
|
| 416 |
+
'for L2, '
|
| 417 |
'self-evicting '
|
| 418 |
'chain (2x L2 in '
|
| 419 |
'touched lines) for '
|
|
|
|
| 425 |
'footprints',
|
| 426 |
'units': 'cycles (single-lane '
|
| 427 |
'dependent loads)',
|
| 428 |
+
'value': {'dram_cycles': 656.9,
|
| 429 |
'l1_cycles': 45.8,
|
| 430 |
+
'l2_cycles': 292.0,
|
| 431 |
'ns_per_cycle': 0.372,
|
| 432 |
'smem_cycles': 30.0}},
|
| 433 |
+
'launch_overhead': {'clock_ghz': 2.705,
|
| 434 |
+
'confidence': 'medium',
|
| 435 |
'method': 'empty-kernel '
|
| 436 |
+
'trains; '
|
| 437 |
+
'torch path '
|
| 438 |
+
'per-launch '
|
| 439 |
+
'through op '
|
| 440 |
+
'dispatch, '
|
| 441 |
+
'raw path '
|
| 442 |
+
'looped '
|
| 443 |
+
'inside one '
|
| 444 |
+
'binding '
|
| 445 |
+
'call',
|
| 446 |
+
'note': 'host-state-dependent; '
|
| 447 |
+
'the '
|
| 448 |
+
'torch-minus-raw '
|
| 449 |
+
'gap is the '
|
| 450 |
+
'dispatch '
|
| 451 |
+
'cost a '
|
| 452 |
+
'torch-launched '
|
| 453 |
+
'candidate '
|
| 454 |
+
'pays',
|
| 455 |
'units': 'us',
|
| 456 |
+
'value': {'raw_queued_us': 6.75,
|
| 457 |
+
'raw_round_trip_us': 13.87,
|
| 458 |
+
'torch_queued_us': 7.09,
|
| 459 |
+
'torch_round_trip_us': 18.53}},
|
| 460 |
+
'mma': {'clock_ghz': 2.705,
|
| 461 |
'confidence': 'medium',
|
| 462 |
+
'fp8_accum': {'fp8_e4m3': {'effective_accum_mantissa_bits': 8,
|
| 463 |
+
'stall_element_value': 16384.0},
|
| 464 |
+
'fp8_e5m2': {'effective_accum_mantissa_bits': 8,
|
| 465 |
+
'stall_element_value': 16384.0}},
|
| 466 |
'method': 'register-fragment '
|
| 467 |
'mma_sync loops (wmma; '
|
| 468 |
'fp8 and 2:4-sparse '
|
| 469 |
'fp16 via inline PTX '
|
| 470 |
+
'm16n8k32); flops '
|
| 471 |
+
'derive from a '
|
| 472 |
+
'sub-saturation '
|
| 473 |
+
'accumulator read-back '
|
| 474 |
+
'with every fragment '
|
| 475 |
+
'element observed, '
|
| 476 |
+
'never the declared '
|
| 477 |
+
'loop shape; sparse '
|
| 478 |
+
'reports executed and '
|
| 479 |
+
'dense-equivalent flops',
|
| 480 |
'note': 'dense-tile rate without '
|
| 481 |
'memory traffic; real '
|
| 482 |
'kernels see this only '
|
|
|
|
| 484 |
'behind it',
|
| 485 |
'units': 'TFLOP/s (TOP/s for '
|
| 486 |
'int8)',
|
| 487 |
+
'value': {'bf16': 369.6,
|
| 488 |
+
'fp16': 365.3,
|
| 489 |
+
'fp16_sparse_2_4': {'dense_equivalent': 711.1,
|
| 490 |
+
'executed': 355.6},
|
| 491 |
+
'fp8_e4m3': 772.3,
|
| 492 |
+
'fp8_e5m2': 771.2,
|
| 493 |
+
'int8': 750.0,
|
| 494 |
+
'tf32': 178.3},
|
| 495 |
+
'verified_k_lanes': {'bf16': {'calibration_linear': True,
|
| 496 |
+
'executed_k_per_iter': 64.0,
|
| 497 |
'vs_nominal': 1.0},
|
| 498 |
+
'fp16': {'calibration_linear': True,
|
| 499 |
+
'executed_k_per_iter': 64.0,
|
| 500 |
'vs_nominal': 1.0},
|
| 501 |
+
'fp16_sparse_2_4': {'calibration_linear': True,
|
| 502 |
+
'executed_k_per_iter': 128.0,
|
| 503 |
'vs_nominal': 1.0},
|
| 504 |
+
'fp8_e4m3': {'calibration_linear': True,
|
| 505 |
+
'executed_k_per_iter': 256.0,
|
| 506 |
+
'vs_nominal': 1.0},
|
| 507 |
+
'fp8_e5m2': {'calibration_linear': True,
|
| 508 |
+
'executed_k_per_iter': 256.0,
|
| 509 |
+
'vs_nominal': 1.0},
|
| 510 |
+
'int8': {'calibration_linear': True,
|
| 511 |
+
'executed_k_per_iter': 64.0,
|
| 512 |
'vs_nominal': 1.0},
|
| 513 |
+
'tf32': {'calibration_linear': True,
|
| 514 |
+
'executed_k_per_iter': 32.0,
|
| 515 |
'vs_nominal': 1.0}}},
|
| 516 |
+
'mma_fed': {'clock_ghz': 2.705,
|
| 517 |
+
'confidence': 'high',
|
| 518 |
+
'method': 'm16n16k16 fp16 '
|
| 519 |
+
'tiles fed from an '
|
| 520 |
+
'L2-resident buffer '
|
| 521 |
+
'through cp.async '
|
| 522 |
+
'double-buffered '
|
| 523 |
+
'shared memory; '
|
| 524 |
+
'accumulator-verified',
|
| 525 |
+
'note': 'the achieved '
|
| 526 |
+
'counterpart to the '
|
| 527 |
+
'register peak: '
|
| 528 |
+
'staging exposed, one '
|
| 529 |
+
'dependent '
|
| 530 |
+
'accumulator chain '
|
| 531 |
+
'per warp',
|
| 532 |
+
'units': 'TFLOP/s',
|
| 533 |
+
'value': 85.2},
|
| 534 |
+
'occupancy_blocks_per_sm': {'clock_ghz': 2.705,
|
| 535 |
'confidence': 'high',
|
| 536 |
'method': 'occupancy '
|
| 537 |
'API '
|
|
|
|
| 560 |
'16KB': 3,
|
| 561 |
'32KB': 3,
|
| 562 |
'48KB': 2}}},
|
| 563 |
+
'occupancy_knees_fma_tflops': {'clock_ghz': 2.705,
|
| 564 |
'confidence': 'high',
|
| 565 |
'method': 'a '
|
| 566 |
'single '
|
|
|
|
| 580 |
'fixed '
|
| 581 |
'total '
|
| 582 |
'work; '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
'the '
|
| 584 |
'cliff '
|
| 585 |
'marks '
|
|
|
|
| 597 |
'above',
|
| 598 |
'units': 'TFLOP/s',
|
| 599 |
'value': {'t1024': {'0KB': 75.5,
|
| 600 |
+
'32KB': 73.71,
|
| 601 |
+
'48KB': 72.69},
|
| 602 |
+
't128': {'0KB': 79.72,
|
| 603 |
'32KB': 60.31,
|
| 604 |
+
'48KB': 45.81},
|
| 605 |
+
't256': {'0KB': 78.79,
|
| 606 |
+
'32KB': 78.85,
|
| 607 |
+
'48KB': 72.97}}},
|
| 608 |
+
'smem_bw': {'clock_ghz': 2.705,
|
| 609 |
'confidence': 'medium',
|
| 610 |
'method': 'block-resident '
|
| 611 |
'read+write '
|
|
|
|
| 614 |
'aggregated across '
|
| 615 |
'SMs',
|
| 616 |
'note': 'bank-conflict-light '
|
| 617 |
+
'access pattern; the '
|
| 618 |
+
'stride sweep below '
|
| 619 |
+
'prices the '
|
| 620 |
+
'conflicted case',
|
| 621 |
'units': 'GB/s',
|
| 622 |
+
'value': 22244.0},
|
| 623 |
+
'smem_conflict_penalty': {'clock_ghz': 2.705,
|
| 624 |
+
'confidence': 'high',
|
| 625 |
+
'method': 'dependent '
|
| 626 |
+
'read-FMA-write '
|
| 627 |
+
'per '
|
| 628 |
+
'thread '
|
| 629 |
+
'at '
|
| 630 |
+
'warp-lane '
|
| 631 |
+
'bank '
|
| 632 |
+
'distance '
|
| 633 |
+
'`stride`; '
|
| 634 |
+
'stride '
|
| 635 |
+
'32 '
|
| 636 |
+
'puts '
|
| 637 |
+
'every '
|
| 638 |
+
'lane '
|
| 639 |
+
'in '
|
| 640 |
+
'one '
|
| 641 |
+
'bank',
|
| 642 |
+
'note': 'the '
|
| 643 |
+
'direct '
|
| 644 |
+
'conflict '
|
| 645 |
+
'measurement '
|
| 646 |
+
'that '
|
| 647 |
+
'disambiguates '
|
| 648 |
+
'a '
|
| 649 |
+
'bank-conflict '
|
| 650 |
+
'signature '
|
| 651 |
+
'from '
|
| 652 |
+
'an ILP '
|
| 653 |
+
'stall',
|
| 654 |
+
'units': 'x '
|
| 655 |
+
'(time '
|
| 656 |
+
'ratio '
|
| 657 |
+
'vs '
|
| 658 |
+
'stride '
|
| 659 |
+
'1)',
|
| 660 |
+
'value': {'stride_1': 1.0,
|
| 661 |
+
'stride_16': 7.13,
|
| 662 |
+
'stride_2': 1.07,
|
| 663 |
+
'stride_32': 14.08,
|
| 664 |
+
'stride_4': 2.04,
|
| 665 |
+
'stride_8': 3.91}},
|
| 666 |
+
'transfer_bw': {'clock_ghz': 2.705,
|
| 667 |
'confidence': 'high',
|
| 668 |
'method': '256 MB torch '
|
| 669 |
'copies, '
|
|
|
|
| 675 |
'direction of '
|
| 676 |
'traffic',
|
| 677 |
'units': 'GB/s',
|
| 678 |
+
'value': {'d2d': 324.0,
|
| 679 |
+
'd2h_pageable': 7.8,
|
| 680 |
+
'd2h_pinned': 26.3,
|
| 681 |
+
'h2d_pageable': 7.6,
|
| 682 |
'h2d_pinned': 24.0}}}}
|
build/torch212-cxx11-cu130-x86_64-linux/metadata.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 7 |
"backend": {
|
| 8 |
"type": "cuda",
|
| 9 |
"archs": [
|
|
@@ -18,21 +19,20 @@
|
|
| 18 |
"digest": {
|
| 19 |
"algorithm": "sha256",
|
| 20 |
"files": {
|
| 21 |
-
"__init__.py": "
|
| 22 |
-
"
|
| 23 |
-
"_ops.py": "
|
| 24 |
-
"_pinned.py": "
|
| 25 |
-
"metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"provenance": {
|
| 29 |
"kernel-builder": {
|
| 30 |
"version": "0.17.0-dev0",
|
| 31 |
-
"sha": "
|
| 32 |
"dirty": false
|
| 33 |
},
|
| 34 |
"kernel": {
|
| 35 |
-
"sha": "
|
| 36 |
"dirty": false
|
| 37 |
}
|
| 38 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
+
"id": "_metakernel_cuda_0b9de42",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
| 7 |
+
"kernel-depends": [],
|
| 8 |
"backend": {
|
| 9 |
"type": "cuda",
|
| 10 |
"archs": [
|
|
|
|
| 19 |
"digest": {
|
| 20 |
"algorithm": "sha256",
|
| 21 |
"files": {
|
| 22 |
+
"__init__.py": "lDQ+fJ0JnI3sQGR3UMnDovBbG4ciYBsaFjmgYdKDTKI=",
|
| 23 |
+
"_metakernel_cuda_0b9de42.abi3.so": "Q8Cgiu34S1d0Bh7mQaIkTyZWlSEDia+KBEAoSVEdmcI=",
|
| 24 |
+
"_ops.py": "x88Bkm1nxRli0nIu2u4ztaJopS1IAZWdN9LZPfspJdo=",
|
| 25 |
+
"_pinned.py": "UyzMniAjfVMIrIbbrSq92KKo5dX5l/GwV3pMVA+eNtM="
|
|
|
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"provenance": {
|
| 29 |
"kernel-builder": {
|
| 30 |
"version": "0.17.0-dev0",
|
| 31 |
+
"sha": "2c40e10d3da1392245ee81bd05b55fb3103b2b34",
|
| 32 |
"dirty": false
|
| 33 |
},
|
| 34 |
"kernel": {
|
| 35 |
+
"sha": "0b9de42fee8a1e52593f2f1f58460c0acc93d7e8",
|
| 36 |
"dirty": false
|
| 37 |
}
|
| 38 |
}
|
build/torch212-cxx11-cu130-x86_64-linux/metakernel/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 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
CHANGED
|
@@ -1,20 +1,4 @@
|
|
| 1 |
-
"""Measuring instrument for the kernel-design loop: timing, adversarial
|
| 2 |
-
correctness comparison, and measured device facts, as loadable ops.
|
| 3 |
-
|
| 4 |
-
from kernels import get_kernel
|
| 5 |
-
mk = get_kernel("phanerozoic/metakernel", version=1,
|
| 6 |
-
trust_remote_code=True)
|
| 7 |
-
|
| 8 |
-
dossier = mk.probe_device() # measured facts
|
| 9 |
-
report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
|
| 10 |
-
verdict = mk.compare(ref, cand, gen) # ULP evidence
|
| 11 |
-
table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
|
| 12 |
-
|
| 13 |
-
Every op returns plain dicts and tensors. Every dossier entry is
|
| 14 |
-
measured by a dedicated probe kernel, carries its SM clock, and labels
|
| 15 |
-
throttled samples; no hardware counters, so entries carry a confidence
|
| 16 |
-
field.
|
| 17 |
-
"""
|
| 18 |
import math
|
| 19 |
import os
|
| 20 |
import time
|
|
@@ -24,9 +8,7 @@ import torch
|
|
| 24 |
try:
|
| 25 |
from ._ops import ops
|
| 26 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 27 |
-
#
|
| 28 |
-
# checkout there is no generated _ops module, so rebuild the op table
|
| 29 |
-
# from the (cached) JIT extension that load_local advertised.
|
| 30 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 31 |
if not _root:
|
| 32 |
raise
|
|
@@ -48,24 +30,25 @@ try:
|
|
| 48 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 49 |
_PINNED = {}
|
| 50 |
|
| 51 |
-
__all__ = ["probe_device", "bench", "compare", "
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"pinned_dossiers", "
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
#
|
| 62 |
_MMA_KINDS = {
|
| 63 |
-
"fp16": (0, 16 * 16, 16,
|
| 64 |
-
"bf16": (1, 16 * 16, 16,
|
| 65 |
-
"tf32": (2, 16 * 16, 8,
|
| 66 |
-
"int8": (3, 16 * 16, 16,
|
| 67 |
-
"fp8_e4m3": (4, 16 * 8, 32,
|
| 68 |
-
"fp16_sparse_2_4": (5, 16 * 8, 32,
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
|
|
@@ -74,7 +57,7 @@ def _sync():
|
|
| 74 |
|
| 75 |
|
| 76 |
def _events(fn, reps):
|
| 77 |
-
"""Time reps calls of fn with one
|
| 78 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 79 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 80 |
for s, e in zip(starts, stops):
|
|
@@ -86,8 +69,7 @@ def _events(fn, reps):
|
|
| 86 |
|
| 87 |
|
| 88 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 89 |
-
"""Effective SM clock in GHz
|
| 90 |
-
Wall time over elapsed ticks is the frequency the ticks ran at."""
|
| 91 |
props = torch.cuda.get_device_properties(device)
|
| 92 |
if blocks is None:
|
| 93 |
blocks = props.multi_processor_count
|
|
@@ -120,8 +102,7 @@ def _entry(value, units, method, clock_ghz, confidence, note=""):
|
|
| 120 |
|
| 121 |
|
| 122 |
def _sattolo_ring(n, device, generator=None):
|
| 123 |
-
"""
|
| 124 |
-
a pointer chase visits every slot before repeating."""
|
| 125 |
perm = torch.randperm(n, generator=generator)
|
| 126 |
ring = torch.empty(n, dtype=torch.int32)
|
| 127 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
@@ -129,8 +110,7 @@ def _sattolo_ring(n, device, generator=None):
|
|
| 129 |
|
| 130 |
|
| 131 |
def _smi_settings(index):
|
| 132 |
-
"""ECC mode, driver,
|
| 133 |
-
when the tool is unavailable."""
|
| 134 |
import subprocess
|
| 135 |
try:
|
| 136 |
out = subprocess.run(
|
|
@@ -150,10 +130,19 @@ def _smi_settings(index):
|
|
| 150 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 151 |
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def mma_tile_check(device="cuda", trials=8):
|
| 154 |
-
"""Random small-integer m16n16k16 tiles through the wmma
|
| 155 |
-
path, compared exactly to the reference GEMM; pins M, N, K, and
|
| 156 |
-
operand mapping (integer operands make fp32 accumulation exact)."""
|
| 157 |
worst = 0.0
|
| 158 |
for t in range(trials):
|
| 159 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
@@ -171,10 +160,83 @@ def mma_tile_check(device="cuda", trials=8):
|
|
| 171 |
"trials": trials}
|
| 172 |
|
| 173 |
|
| 174 |
-
def
|
| 175 |
-
"""
|
| 176 |
-
|
| 177 |
-
a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
props = torch.cuda.get_device_properties(device)
|
| 179 |
sm = props.multi_processor_count
|
| 180 |
l2 = props.L2_cache_size
|
|
@@ -187,8 +249,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 187 |
"vram_bytes": props.total_memory,
|
| 188 |
"torch": torch.__version__,
|
| 189 |
"cuda_runtime": torch.version.cuda,
|
| 190 |
-
# instrument settings
|
| 191 |
-
# dossier records them (None when nvidia-smi is unavailable)
|
| 192 |
**_smi_settings(torch.device(device).index or 0),
|
| 193 |
}
|
| 194 |
}
|
|
@@ -213,9 +274,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 213 |
}
|
| 214 |
clk = max(traj)
|
| 215 |
|
| 216 |
-
# ---- device-memory bandwidth
|
| 217 |
-
|
| 218 |
-
n4 = 16 << 20 # 16M float4 = 256 MB per buffer
|
| 219 |
if props.total_memory < 4 << 30:
|
| 220 |
n4 = 4 << 20
|
| 221 |
a = torch.rand(n4 * 4, device=device)
|
|
@@ -239,7 +299,6 @@ def probe_device(device="cuda", quick=False):
|
|
| 239 |
dtype=torch.int32).to(device)
|
| 240 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 241 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
| 242 |
-
# traffic accounting: idx reads + 16B random loads + streamed stores
|
| 243 |
hbm["random_gather_16B"] = round(
|
| 244 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 245 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
@@ -250,9 +309,9 @@ def probe_device(device="cuda", quick=False):
|
|
| 250 |
"so its nominal number is the achievable rate for "
|
| 251 |
"random access, not a line-efficiency claim")
|
| 252 |
|
| 253 |
-
# ---- L2 bandwidth
|
| 254 |
ws = max(l2 // 4, 1 << 20)
|
| 255 |
-
nl2 = ws // 16
|
| 256 |
al = a[:nl2 * 4].contiguous()
|
| 257 |
bl = b[:nl2 * 4].contiguous()
|
| 258 |
cl = c[:nl2 * 4].contiguous()
|
|
@@ -267,62 +326,61 @@ def probe_device(device="cuda", quick=False):
|
|
| 267 |
"medium", "first pass streams from HBM; amortized "
|
| 268 |
f"over {passes} passes")
|
| 269 |
|
| 270 |
-
# ---- latencies:
|
| 271 |
-
# L1, fits L2 everywhere, and stays TLB-resident after a warm walk;
|
| 272 |
-
# the DRAM chain touches 2x L2 in lines, so it evicts itself.
|
| 273 |
lat = {}
|
| 274 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
| 275 |
-
ring_s = _sattolo_ring(8192, device)
|
| 276 |
-
ops.mk_chase_shared(ring_s, 1 << 14, out64)
|
| 277 |
-
_sync()
|
| 278 |
-
hops = 1 << 16
|
| 279 |
-
ops.mk_chase_shared(ring_s, hops, out64)
|
| 280 |
-
_sync()
|
| 281 |
-
lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
_sync()
|
| 299 |
-
lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 302 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 303 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
_sync()
|
| 308 |
-
lat["dram_cycles"] = round(int(out64[0].item()) / hops_h, 1)
|
| 309 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 310 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 311 |
-
"Sattolo-cycle pointer chase
|
| 312 |
-
"2 MB TLB-resident ring for L2,
|
| 313 |
-
"chain (2x L2 in touched lines)
|
| 314 |
-
idle_clk, "high",
|
| 315 |
"smem includes index arithmetic; HBM includes "
|
| 316 |
"residual TLB effects at large footprints")
|
| 317 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 318 |
|
| 319 |
-
# ---- fp32 FMA and tensor-core throughput
|
| 320 |
blocks = sm * 4
|
| 321 |
iters = 20_000 if quick else 60_000
|
| 322 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 323 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 324 |
reps))
|
| 325 |
-
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 326 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 327 |
"register-resident FMA chains", clk, "high")
|
| 328 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
@@ -334,43 +392,88 @@ def probe_device(device="cuda", quick=False):
|
|
| 334 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 335 |
"register-resident double FMA chains (the DFMA "
|
| 336 |
"pipe)", clk, "high")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
mma = {}
|
| 338 |
mma_klanes = {}
|
| 339 |
-
|
|
|
|
| 340 |
it = 30_000 if quick else 100_000
|
| 341 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 342 |
if r != 0:
|
| 343 |
mma[name] = "unsupported"
|
| 344 |
continue
|
| 345 |
_sync()
|
| 346 |
-
|
| 347 |
-
# the executed K-lane count (exact at these magnitudes)
|
| 348 |
-
cal_iters = 512
|
| 349 |
-
sink[1] = 0.0
|
| 350 |
-
ops.mk_mma(kind, 1, 1, cal_iters, sink)
|
| 351 |
-
_sync()
|
| 352 |
-
gain_per_iter = float(sink[1].item()) / cal_iters
|
| 353 |
mma_klanes[name] = {
|
| 354 |
-
"executed_k_per_iter": round(
|
| 355 |
-
"vs_nominal": round(
|
|
|
|
| 356 |
}
|
|
|
|
|
|
|
| 357 |
ms = _median(_events(
|
| 358 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 362 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 363 |
-
"2:4-sparse fp16 via inline PTX m16n8k32;
|
| 364 |
-
"
|
| 365 |
-
"flops derive from the mean accumulator read-back "
|
| 366 |
"with every fragment element observed, never the "
|
| 367 |
-
"declared loop shape"
|
|
|
|
| 368 |
clk, "medium",
|
| 369 |
"dense-tile rate without memory traffic; real "
|
| 370 |
"kernels see this only when staging hides behind it")
|
| 371 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
# ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
it_sm = 30_000 if quick else 100_000
|
| 375 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 376 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
@@ -380,8 +483,23 @@ def probe_device(device="cuda", quick=False):
|
|
| 380 |
"block-resident read+write streaming (8 B per "
|
| 381 |
"thread-iteration), aggregated across SMs", clk,
|
| 382 |
"medium",
|
| 383 |
-
"bank-conflict-light access pattern;
|
| 384 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
# ---- PCIe / host transfer bandwidth
|
| 387 |
nbytes = 256 << 20
|
|
@@ -412,9 +530,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 412 |
"d2d counts one direction of traffic")
|
| 413 |
del host_pin, host_pag, dev_t, dev_u
|
| 414 |
|
| 415 |
-
# ---- atomic contention curve
|
| 416 |
-
# addresses the whole grid hammers (slots = 1 is full contention;
|
| 417 |
-
# slots = threads is contention-free), plus the shared-memory point
|
| 418 |
n_thr = blocks * 256
|
| 419 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 420 |
at = {"curve_slots": {}}
|
|
@@ -424,7 +540,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 424 |
buf.zero_()
|
| 425 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 426 |
ms = _median(_events(
|
| 427 |
-
lambda
|
| 428 |
reps))
|
| 429 |
at["curve_slots"][str(slots)] = round(
|
| 430 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
@@ -437,7 +553,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 437 |
"of distinct global addresses from 1 (full "
|
| 438 |
"contention) to one per thread", clk, "high")
|
| 439 |
|
| 440 |
-
# ---- launch overhead
|
| 441 |
anchor = sink
|
| 442 |
n_launch = 500 if quick else 2000
|
| 443 |
_sync()
|
|
@@ -452,10 +568,19 @@ def probe_device(device="cuda", quick=False):
|
|
| 452 |
ops.mk_empty(anchor)
|
| 453 |
_sync()
|
| 454 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
|
|
|
|
|
|
| 455 |
d["launch_overhead"] = _entry(
|
| 456 |
-
{"
|
| 457 |
-
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
# ---- grid barrier
|
| 461 |
bar_iters = 500 if quick else 2000
|
|
@@ -471,9 +596,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 471 |
"unavailable or grid too large", clk,
|
| 472 |
"high")
|
| 473 |
|
| 474 |
-
# ---- occupancy
|
| 475 |
-
# at every (threads, dyn_smem) point; when the forced blocks/SM stop
|
| 476 |
-
# covering memory latency, achieved bandwidth falls off the cliff
|
| 477 |
occ = {}
|
| 478 |
for threads in (128, 256, 512, 1024):
|
| 479 |
row = {}
|
|
@@ -494,8 +617,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 494 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 495 |
knee_iters, sink)
|
| 496 |
ms = _median(_events(
|
| 497 |
-
lambda t=threads,
|
| 498 |
-
knee_blocks, t,
|
| 499 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 500 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 501 |
knees[f"t{threads}"] = row
|
|
@@ -504,27 +627,160 @@ def probe_device(device="cuda", quick=False):
|
|
| 504 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 505 |
"the cliff marks where occupancy stops covering latency",
|
| 506 |
clk, "high", "timing-derived counterpart to the API table above")
|
|
|
|
|
|
|
|
|
|
| 507 |
return d
|
| 508 |
|
| 509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 511 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 512 |
-
device="cuda"):
|
| 513 |
-
"""Median-of-N cudaEvent timing with throttle rejection.
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
around every sample, ~0.3 ms each."""
|
| 523 |
for _ in range(warmup):
|
| 524 |
fn()
|
| 525 |
_sync()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
graph_state = "off"
|
| 527 |
graph_reason = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
if graph:
|
| 529 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 530 |
try:
|
|
@@ -539,10 +795,7 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 539 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 540 |
graph_state = "fallback_eager"
|
| 541 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 542 |
-
# drain the invalidated stream
|
| 543 |
-
# On some torch versions a failed capture also wedges the
|
| 544 |
-
# device RNG with no in-process recovery; timing uses no RNG,
|
| 545 |
-
# but the caller's later randn may raise.
|
| 546 |
rng_ok = True
|
| 547 |
for _attempt in range(5):
|
| 548 |
try:
|
|
@@ -572,25 +825,51 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 572 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 573 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 574 |
best_clk = max(clocks)
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
flat = []
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
kept.append(ms)
|
| 585 |
else:
|
| 586 |
rejected += 1
|
| 587 |
-
|
|
|
|
| 588 |
kept = flat
|
| 589 |
med = _median(kept)
|
| 590 |
ks = sorted(kept)
|
| 591 |
n_k = len(ks)
|
| 592 |
-
# order-statistic 95% CI on the median
|
| 593 |
-
# binomial ranks); degenerate at tiny n, where it spans the sample
|
| 594 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 595 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 596 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
@@ -605,12 +884,25 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 605 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 606 |
"samples": len(flat),
|
| 607 |
"rejected_throttled": rejected,
|
|
|
|
| 608 |
"graph_captured": graph_state == "captured",
|
| 609 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 610 |
"worst": round(min(clocks), 3)},
|
| 611 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
if graph_reason:
|
| 613 |
rep["graph_fallback_reason"] = graph_reason
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
if bytes is not None:
|
| 615 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 616 |
if flops is not None:
|
|
@@ -620,6 +912,8 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 620 |
if dossier is not None and bytes and flops:
|
| 621 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 622 |
mma_v = dossier["mma"]["value"].get(dtype)
|
|
|
|
|
|
|
| 623 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 624 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 625 |
ai = flops / bytes
|
|
@@ -630,13 +924,12 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 630 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 631 |
"peak_dtype": dtype,
|
| 632 |
}
|
|
|
|
| 633 |
return rep
|
| 634 |
|
| 635 |
|
| 636 |
def _to_ordered_int(t):
|
| 637 |
-
"""
|
| 638 |
-
distance: positive floats keep their bits, negative floats mirror
|
| 639 |
-
below zero, so the mapping is monotone in the float value."""
|
| 640 |
if t.dtype == torch.float32:
|
| 641 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 642 |
half = 1 << 31
|
|
@@ -652,11 +945,12 @@ def _to_ordered_int(t):
|
|
| 652 |
|
| 653 |
|
| 654 |
def ulp_diff(a, b):
|
| 655 |
-
"""Elementwise ULP distance
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
|
|
|
| 660 |
if not a.is_floating_point():
|
| 661 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 662 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
@@ -673,9 +967,7 @@ class order_shuffles:
|
|
| 673 |
|
| 674 |
@staticmethod
|
| 675 |
def last_dim():
|
| 676 |
-
"""
|
| 677 |
-
accumulation order of a last-dim reduction changes, the value
|
| 678 |
-
does not."""
|
| 679 |
def shuffle(args, seed):
|
| 680 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 681 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
@@ -685,8 +977,7 @@ class order_shuffles:
|
|
| 685 |
|
| 686 |
@staticmethod
|
| 687 |
def matmul_k():
|
| 688 |
-
"""
|
| 689 |
-
product is unchanged, the accumulation order is not."""
|
| 690 |
def shuffle(args, seed):
|
| 691 |
a_m, b_m = args[0], args[1]
|
| 692 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
@@ -700,37 +991,70 @@ def _as_outputs(x):
|
|
| 700 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 701 |
|
| 702 |
|
| 703 |
-
def
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
worst = {"max_ulp": -1}
|
| 722 |
total_hist = {}
|
| 723 |
per_trial = []
|
| 724 |
max_abs_seen = 0.0
|
| 725 |
max_rel_seen = 0.0
|
|
|
|
| 726 |
for t in range(trials):
|
| 727 |
args = gen(seed + t)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
r_outs = _as_outputs(ref(*args))
|
| 729 |
c_outs = _as_outputs(cand(*args))
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
bands = None
|
| 735 |
band = 0
|
| 736 |
band_score = 0.0
|
|
@@ -754,8 +1078,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 754 |
max_ulp = 0
|
| 755 |
mean_ulp = 0.0
|
| 756 |
first_div = -1
|
|
|
|
| 757 |
n_el = 0
|
| 758 |
n_above_band = 0
|
|
|
|
|
|
|
| 759 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 760 |
worst_elements = []
|
| 761 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
@@ -773,8 +1100,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 773 |
mean_ulp += float(d.double().sum().item())
|
| 774 |
n_el += d.numel()
|
| 775 |
nz = (d > 0)
|
|
|
|
|
|
|
| 776 |
if first_div < 0 and nz.any():
|
| 777 |
first_div = int(nz.flatten().nonzero()[0].item())
|
|
|
|
| 778 |
if nz.any():
|
| 779 |
buckets = torch.clamp(
|
| 780 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
@@ -791,6 +1121,7 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 791 |
if val > 0:
|
| 792 |
worst_elements.append({
|
| 793 |
"output": oi, "index": int(ix),
|
|
|
|
| 794 |
"ref": float(rf[ix].item()),
|
| 795 |
"cand": float(cf[ix].item()),
|
| 796 |
"ulp": int(val)})
|
|
@@ -810,23 +1141,27 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 810 |
inf_r += int(torch.isinf(r).sum().item())
|
| 811 |
inf_c += int(torch.isinf(c).sum().item())
|
| 812 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
|
|
|
| 813 |
trial_rep = {
|
| 814 |
"max_ulp": max_ulp,
|
| 815 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 816 |
"first_divergent_index": first_div,
|
|
|
|
| 817 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 818 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 819 |
"order_band_ulp": band,
|
| 820 |
"band_score": round(band_score, 2),
|
| 821 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
|
|
|
|
|
|
| 822 |
"worst_elements": worst_elements[:worst_k],
|
| 823 |
}
|
| 824 |
per_trial.append(trial_rep)
|
| 825 |
if max_ulp > worst["max_ulp"]:
|
| 826 |
worst = trial_rep
|
| 827 |
-
band_max = max(t["order_band_ulp"] for t in per_trial)
|
| 828 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 829 |
-
|
|
|
|
| 830 |
score = max(t["band_score"] for t in per_trial)
|
| 831 |
ulp_ok = score <= band_margin
|
| 832 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
@@ -841,8 +1176,34 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 841 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 842 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 843 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 844 |
-
|
| 845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
"criterion": criterion,
|
| 847 |
"worst_trial": worst,
|
| 848 |
"max_abs": max_abs_seen,
|
|
@@ -850,6 +1211,9 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 850 |
"trials": per_trial,
|
| 851 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 852 |
}
|
|
|
|
|
|
|
|
|
|
| 853 |
|
| 854 |
|
| 855 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
@@ -865,6 +1229,30 @@ def _context_alive(device):
|
|
| 865 |
return False
|
| 866 |
|
| 867 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
def _fuzz_build(make, shape, dtype):
|
| 869 |
try:
|
| 870 |
out = make(tuple(shape), dtype)
|
|
@@ -881,7 +1269,7 @@ def _fuzz_variant(x, v):
|
|
| 881 |
if v == "strided_rows":
|
| 882 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 883 |
if v == "offset_slice":
|
| 884 |
-
#
|
| 885 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 886 |
if v == "overflow_expand":
|
| 887 |
if x.numel() == 0:
|
|
@@ -895,8 +1283,7 @@ def _fuzz_variant(x, v):
|
|
| 895 |
|
| 896 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 897 |
stop_on_context_loss=True):
|
| 898 |
-
"""All variant cases for one (shape, dtype) group; returns (rows,
|
| 899 |
-
context_lost)."""
|
| 900 |
rows = []
|
| 901 |
bases = _fuzz_build(make, shape, dtype)
|
| 902 |
lost = False
|
|
@@ -933,8 +1320,7 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 933 |
got = op(*xs)
|
| 934 |
want = ref(*xs)
|
| 935 |
if v == "overflow_expand":
|
| 936 |
-
# bounded comparison:
|
| 937 |
-
# which is where int32 flat indexing wraps
|
| 938 |
w = 1 << 20
|
| 939 |
gf, wf = got.flatten(), want.flatten()
|
| 940 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
@@ -959,20 +1345,30 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 959 |
return rows, lost
|
| 960 |
|
| 961 |
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
import
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 974 |
meta = json.load(open(sys.argv[1], "r"))
|
| 975 |
-
sys.path[:0] = meta["sys_path"]
|
| 976 |
try:
|
| 977 |
import kernels
|
| 978 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
@@ -985,85 +1381,165 @@ except Exception:
|
|
| 985 |
import load_local
|
| 986 |
_mk = load_local.load()
|
| 987 |
import torch
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 994 |
"""
|
| 995 |
|
| 996 |
|
| 997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 999 |
-
isolate_timeout_s=180, device="cuda"):
|
| 1000 |
-
"""
|
| 1001 |
-
report ok / mismatch / exception per case.
|
| 1002 |
-
|
| 1003 |
-
`make(shape, dtype)` (or `make(shape)`) builds a tensor or tuple;
|
| 1004 |
-
layout variants derive non-contiguous and misaligned views whose
|
| 1005 |
-
content equals a contiguous copy. `overflow_expand` stride-0-expands
|
| 1006 |
-
past 2**31 logical elements to catch int32 indexing (`skipped_vram`
|
| 1007 |
-
when it would not fit). Every case carries a `repro` recipe.
|
| 1008 |
-
In-process, the CUDA context is health-checked after exceptions and
|
| 1009 |
-
remaining cases marked `context_lost` if killed; `isolate=True`
|
| 1010 |
-
spawns each (shape, dtype) group in a subprocess, so a
|
| 1011 |
-
context-killing candidate costs one group (`crashed_isolated`);
|
| 1012 |
-
op/ref/make must be picklable."""
|
| 1013 |
if shapes is None:
|
| 1014 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1015 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
|
|
|
|
|
|
| 1016 |
|
| 1017 |
rows = []
|
| 1018 |
lost = False
|
| 1019 |
if isolate:
|
| 1020 |
import json
|
| 1021 |
import pickle
|
| 1022 |
-
import subprocess
|
| 1023 |
import sys as _sys
|
| 1024 |
import tempfile
|
| 1025 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1026 |
fns = os.path.join(tmpdir, "fns.pkl")
|
|
|
|
|
|
|
| 1027 |
with open(fns, "wb") as f:
|
| 1028 |
-
pickle.dump(
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
"shape": list(shape),
|
| 1041 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1042 |
"variants": list(variants),
|
| 1043 |
"ulp_tol": ulp_tol, "device": device,
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
try:
|
| 1048 |
-
r = subprocess.run(
|
| 1049 |
-
[_sys.executable, "-c", _ISOLATE_BOOTSTRAP, meta,
|
| 1050 |
-
fns, out], timeout=isolate_timeout_s,
|
| 1051 |
-
capture_output=True)
|
| 1052 |
-
if r.returncode == 0 and os.path.exists(out):
|
| 1053 |
with open(out, "rb") as f:
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
"status": "crashed_isolated",
|
| 1064 |
-
"exitcode": exitcode,
|
| 1065 |
-
"stderr_tail": stderr_tail}]
|
| 1066 |
-
rows.extend(group)
|
| 1067 |
else:
|
| 1068 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1069 |
for gi, (dtype, shape) in enumerate(pending):
|
|
@@ -1080,21 +1556,27 @@ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
|
|
| 1080 |
n_bad = sum(1 for r in rows
|
| 1081 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1082 |
"skipped_empty"))
|
| 1083 |
-
|
| 1084 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
|
| 1086 |
|
| 1087 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1088 |
check=None, budget_s=None, race=False):
|
| 1089 |
-
"""Launch-configuration search
|
| 1090 |
-
bench row per grid point, sorted fastest first. A point whose factory
|
| 1091 |
-
or run raises is reported, not skipped silently. `check(fn) -> bool`
|
| 1092 |
-
is an optional correctness gate recorded per row (a fast wrong config
|
| 1093 |
-
must not win a sweep). `budget_s` caps total sweep time; points not
|
| 1094 |
-
reached are reported as `not_run` rather than silently dropped.
|
| 1095 |
-
`race=True` runs successive halving: a quick pass over every point at
|
| 1096 |
-
iters/4, then the full budget only on the fastest quartile; pruned
|
| 1097 |
-
points keep their quick measurement and status `pruned_race`."""
|
| 1098 |
keys = list(grid.keys())
|
| 1099 |
|
| 1100 |
def expand(i, acc):
|
|
@@ -1159,13 +1641,71 @@ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
|
| 1159 |
continue
|
| 1160 |
rows.append(run_point(p, iters))
|
| 1161 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
return rows
|
| 1163 |
|
| 1164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1165 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1166 |
-
"""Device buffer for
|
| 1167 |
-
stamps.cuh): the candidate kernel writes clock64() into slot i. With
|
| 1168 |
-
`blocks`, allocates the [blocks, n] layout for MK_STAMP_BLOCK."""
|
| 1169 |
if blocks is not None:
|
| 1170 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1171 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
@@ -1175,8 +1715,7 @@ _DRIFT_SKIP = ("device", "clock", "launch_overhead", "transfer_bw")
|
|
| 1175 |
|
| 1176 |
|
| 1177 |
def pinned_dossiers():
|
| 1178 |
-
"""Names of the
|
| 1179 |
-
architecture)."""
|
| 1180 |
return sorted(_PINNED.keys())
|
| 1181 |
|
| 1182 |
|
|
@@ -1195,11 +1734,7 @@ def _numeric_leaves(d, prefix=""):
|
|
| 1195 |
|
| 1196 |
|
| 1197 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1198 |
-
"""
|
| 1199 |
-
same architecture: per-leaf relative drift, worst drift, entries
|
| 1200 |
-
over `rel_tol`. Host-dependent entries and raw clocks are skipped by
|
| 1201 |
-
default (`skip=()` compares everything); `pinned=None` auto-selects
|
| 1202 |
-
by sm_arch and raises if no pin exists."""
|
| 1203 |
if pinned is None:
|
| 1204 |
arch = current["device"]["sm_arch"]
|
| 1205 |
matches = [k for k, v in _PINNED.items()
|
|
@@ -1222,16 +1757,19 @@ def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
|
| 1222 |
"rel": round(rel, 4)})
|
| 1223 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1224 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
|
| 1229 |
|
| 1230 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1231 |
device="cuda"):
|
| 1232 |
-
"""Run the shipped MK_STAMP_BLOCK example
|
| 1233 |
-
compute, global store) and decode its stamps; a template for
|
| 1234 |
-
instrumenting a candidate."""
|
| 1235 |
props = torch.cuda.get_device_properties(device)
|
| 1236 |
if blocks is None:
|
| 1237 |
blocks = props.multi_processor_count
|
|
@@ -1244,11 +1782,7 @@ def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
|
| 1244 |
|
| 1245 |
|
| 1246 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1247 |
-
"""Decode MK_STAMP
|
| 1248 |
-
microseconds under the measured (or provided) SM clock. A 1-D buffer
|
| 1249 |
-
(MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
|
| 1250 |
-
buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
|
| 1251 |
-
phase, with blocks whose stamps stayed zero excluded."""
|
| 1252 |
if clock_ghz is None:
|
| 1253 |
clock_ghz = measure_clock(stamps.device)
|
| 1254 |
|
|
@@ -1284,3 +1818,104 @@ def read_stamps(stamps, labels=None, clock_ghz=None):
|
|
| 1284 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1285 |
"blocks_reporting": int(live.sum().item()),
|
| 1286 |
"blocks_total": int(live.numel())}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Measuring instrument for the kernel-design loop: measured device facts, throttle-aware timing, adversarial correctness comparison, all as loadable ops returning plain data."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import math
|
| 3 |
import os
|
| 4 |
import time
|
|
|
|
| 8 |
try:
|
| 9 |
from ._ops import ops
|
| 10 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 11 |
+
# children re-import the package; a source checkout rebuilds the op table from the cached JIT extension
|
|
|
|
|
|
|
| 12 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 13 |
if not _root:
|
| 14 |
raise
|
|
|
|
| 30 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 31 |
_PINNED = {}
|
| 32 |
|
| 33 |
+
__all__ = ["probe_device", "probe_all_devices", "bench", "compare", "judge",
|
| 34 |
+
"order_shuffles", "fuzz_shapes", "sweep", "alloc_stamps",
|
| 35 |
+
"read_stamps", "stamped_demo", "measure_clock", "ulp_diff",
|
| 36 |
+
"dossier_drift", "pinned_dossiers", "mma_tile_check", "schema",
|
| 37 |
+
"probe_capturable", "ops"]
|
| 38 |
+
|
| 39 |
+
_REPO_ID = "phanerozoic/metakernel"
|
| 40 |
+
_REPO_VERSION = 1
|
| 41 |
+
_PROBE_SCHEMA = 2
|
| 42 |
+
|
| 43 |
+
# kind id, C-tile M*N, nominal K, nominal K-lanes per unrolled loop iteration
|
| 44 |
_MMA_KINDS = {
|
| 45 |
+
"fp16": (0, 16 * 16, 16, 64.0),
|
| 46 |
+
"bf16": (1, 16 * 16, 16, 64.0),
|
| 47 |
+
"tf32": (2, 16 * 16, 8, 32.0),
|
| 48 |
+
"int8": (3, 16 * 16, 16, 64.0),
|
| 49 |
+
"fp8_e4m3": (4, 16 * 8, 32, 256.0),
|
| 50 |
+
"fp16_sparse_2_4": (5, 16 * 8, 32, 128.0),
|
| 51 |
+
"fp8_e5m2": (6, 16 * 8, 32, 256.0),
|
| 52 |
}
|
| 53 |
|
| 54 |
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def _events(fn, reps):
|
| 60 |
+
"""Time reps calls of fn with one cudaEvent pair each; returns a ms list."""
|
| 61 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 62 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 63 |
for s, e in zip(starts, stops):
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 72 |
+
"""Effective SM clock in GHz from clock64 ticks over wall time under an FMA spin."""
|
|
|
|
| 73 |
props = torch.cuda.get_device_properties(device)
|
| 74 |
if blocks is None:
|
| 75 |
blocks = props.multi_processor_count
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
def _sattolo_ring(n, device, generator=None):
|
| 105 |
+
"""Random single-cycle permutation as an int32 next-pointer table."""
|
|
|
|
| 106 |
perm = torch.randperm(n, generator=generator)
|
| 107 |
ring = torch.empty(n, dtype=torch.int32)
|
| 108 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
|
|
| 110 |
|
| 111 |
|
| 112 |
def _smi_settings(index):
|
| 113 |
+
"""ECC mode, driver, power limit via nvidia-smi; None fields when unavailable."""
|
|
|
|
| 114 |
import subprocess
|
| 115 |
try:
|
| 116 |
out = subprocess.run(
|
|
|
|
| 130 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 131 |
|
| 132 |
|
| 133 |
+
def _unravel(flat, shape):
|
| 134 |
+
"""Multi-dimensional index for a flat index into shape."""
|
| 135 |
+
if not shape:
|
| 136 |
+
return []
|
| 137 |
+
out = []
|
| 138 |
+
for s in reversed(list(shape)):
|
| 139 |
+
out.append(int(flat % s))
|
| 140 |
+
flat //= s
|
| 141 |
+
return list(reversed(out))
|
| 142 |
+
|
| 143 |
+
|
| 144 |
def mma_tile_check(device="cuda", trials=8):
|
| 145 |
+
"""Random small-integer m16n16k16 tiles through the wmma path, compared exactly to the reference GEMM."""
|
|
|
|
|
|
|
| 146 |
worst = 0.0
|
| 147 |
for t in range(trials):
|
| 148 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
|
|
| 160 |
"trials": trials}
|
| 161 |
|
| 162 |
|
| 163 |
+
def _tenancy_canary(device, rel_tol=0.10, attempts=3):
|
| 164 |
+
"""Fixed-cost triad timed in paired rounds; busy only when every attempt disagrees."""
|
| 165 |
+
n4 = 4 << 20
|
| 166 |
+
a = torch.rand(n4 * 4, device=device)
|
| 167 |
+
b = torch.rand(n4 * 4, device=device)
|
| 168 |
+
c = torch.empty(n4 * 4, device=device)
|
| 169 |
+
ops.mk_triad(a, b, c, 4, 1.5)
|
| 170 |
+
rels = []
|
| 171 |
+
for _ in range(attempts):
|
| 172 |
+
r1 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 173 |
+
r2 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 174 |
+
rel = abs(r1 - r2) / min(r1, r2)
|
| 175 |
+
rels.append(round(rel, 4))
|
| 176 |
+
if rel <= rel_tol:
|
| 177 |
+
return False, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 178 |
+
return True, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _cache_dir():
|
| 182 |
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~/.cache")
|
| 183 |
+
d = os.path.join(base, "metakernel")
|
| 184 |
+
os.makedirs(d, exist_ok=True)
|
| 185 |
+
return d
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _cache_path(device, quick):
|
| 189 |
+
import re
|
| 190 |
+
props = torch.cuda.get_device_properties(device)
|
| 191 |
+
smi = _smi_settings(torch.device(device).index or 0)
|
| 192 |
+
slug = re.sub(r"[^a-z0-9]+", "", props.name.lower())[:24]
|
| 193 |
+
key = "sm%d%d_%s_drv%s_cu%s_q%d_s%d" % (
|
| 194 |
+
props.major, props.minor, slug, smi["driver"] or "na",
|
| 195 |
+
torch.version.cuda, int(quick), _PROBE_SCHEMA)
|
| 196 |
+
return os.path.join(_cache_dir(), key + ".json")
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _mma_calibrate(kind_id, sink):
|
| 200 |
+
"""Executed K-lanes per loop iteration from a sub-saturation accumulator read-back, with a linearity check."""
|
| 201 |
+
gains = []
|
| 202 |
+
for cal in (4, 32):
|
| 203 |
+
sink[1] = 0.0
|
| 204 |
+
ops.mk_mma(kind_id, 1, 1, cal, sink)
|
| 205 |
+
_sync()
|
| 206 |
+
gains.append(float(sink[1].item()) / cal)
|
| 207 |
+
linear = gains[0] > 0 and abs(gains[1] - gains[0]) / gains[0] < 0.02
|
| 208 |
+
return gains[0], linear
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _fp8_accum_stall(kind_id, sink, k_exec):
|
| 212 |
+
"""Growth curve of the fp8 accumulator; a stall reveals the effective accumulate mantissa width."""
|
| 213 |
+
vals = []
|
| 214 |
+
for it in (32, 64, 128, 256, 512):
|
| 215 |
+
sink[1] = 0.0
|
| 216 |
+
ops.mk_mma(kind_id, 1, 1, it, sink)
|
| 217 |
+
_sync()
|
| 218 |
+
vals.append(float(sink[1].item()) / 2.0)
|
| 219 |
+
if vals[-1] == vals[-2] and vals[-1] < vals[0] * 16 * 0.99:
|
| 220 |
+
bits = int(round(math.log2(vals[-1] / (2.0 * k_exec))))
|
| 221 |
+
return {"stall_element_value": vals[-1],
|
| 222 |
+
"effective_accum_mantissa_bits": bits}
|
| 223 |
+
return {"stall_element_value": None,
|
| 224 |
+
"effective_accum_mantissa_bits": None}
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def probe_device(device="cuda", quick=False, cached=True,
|
| 228 |
+
tenancy_check=True):
|
| 229 |
+
"""Empirical device dossier; every entry measured by a dedicated probe kernel and stamped with its SM clock."""
|
| 230 |
+
cpath = _cache_path(device, quick)
|
| 231 |
+
if cached and os.path.exists(cpath):
|
| 232 |
+
import json
|
| 233 |
+
return json.load(open(cpath, encoding="utf-8"))
|
| 234 |
+
if tenancy_check:
|
| 235 |
+
busy, ev = _tenancy_canary(device)
|
| 236 |
+
if busy:
|
| 237 |
+
raise RuntimeError("device_busy: canary rounds disagree %s; "
|
| 238 |
+
"idle the device or pass "
|
| 239 |
+
"tenancy_check=False" % ev)
|
| 240 |
props = torch.cuda.get_device_properties(device)
|
| 241 |
sm = props.multi_processor_count
|
| 242 |
l2 = props.L2_cache_size
|
|
|
|
| 249 |
"vram_bytes": props.total_memory,
|
| 250 |
"torch": torch.__version__,
|
| 251 |
"cuda_runtime": torch.version.cuda,
|
| 252 |
+
# instrument settings recorded because readings depend on them
|
|
|
|
| 253 |
**_smi_settings(torch.device(device).index or 0),
|
| 254 |
}
|
| 255 |
}
|
|
|
|
| 274 |
}
|
| 275 |
clk = max(traj)
|
| 276 |
|
| 277 |
+
# ---- device-memory bandwidth per access width plus read/write/gather
|
| 278 |
+
n4 = 16 << 20
|
|
|
|
| 279 |
if props.total_memory < 4 << 30:
|
| 280 |
n4 = 4 << 20
|
| 281 |
a = torch.rand(n4 * 4, device=device)
|
|
|
|
| 299 |
dtype=torch.int32).to(device)
|
| 300 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 301 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
|
|
|
| 302 |
hbm["random_gather_16B"] = round(
|
| 303 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 304 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
|
|
| 309 |
"so its nominal number is the achievable rate for "
|
| 310 |
"random access, not a line-efficiency claim")
|
| 311 |
|
| 312 |
+
# ---- L2 bandwidth over an L2-resident working set
|
| 313 |
ws = max(l2 // 4, 1 << 20)
|
| 314 |
+
nl2 = ws // 16
|
| 315 |
al = a[:nl2 * 4].contiguous()
|
| 316 |
bl = b[:nl2 * 4].contiguous()
|
| 317 |
cl = c[:nl2 * 4].contiguous()
|
|
|
|
| 326 |
"medium", "first pass streams from HBM; amortized "
|
| 327 |
f"over {passes} passes")
|
| 328 |
|
| 329 |
+
# ---- latencies: min of 3 chases per level so a co-tenant burst cannot land in the dossier
|
|
|
|
|
|
|
| 330 |
lat = {}
|
| 331 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
+
def chase(op, ring, warm_hops, hops, extra=None):
|
| 334 |
+
best = None
|
| 335 |
+
for _ in range(3):
|
| 336 |
+
if extra is None:
|
| 337 |
+
op(ring, warm_hops, out64)
|
| 338 |
+
_sync()
|
| 339 |
+
op(ring, hops, out64)
|
| 340 |
+
else:
|
| 341 |
+
op(ring, extra, warm_hops, out64)
|
| 342 |
+
_sync()
|
| 343 |
+
op(ring, extra, hops, out64)
|
| 344 |
+
_sync()
|
| 345 |
+
t = int(out64[0].item()) / hops
|
| 346 |
+
best = t if best is None else min(best, t)
|
| 347 |
+
return best
|
|
|
|
|
|
|
| 348 |
|
| 349 |
+
ring_s = _sattolo_ring(8192, device)
|
| 350 |
+
lat["smem_cycles"] = round(
|
| 351 |
+
chase(lambda r, h, o: ops.mk_chase_shared(r, h, o), ring_s,
|
| 352 |
+
1 << 14, 1 << 16), 1)
|
| 353 |
+
ring_l1 = _sattolo_ring((16 << 10) // 4, device)
|
| 354 |
+
lat["l1_cycles"] = round(
|
| 355 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l1,
|
| 356 |
+
ring_l1.numel(), 1 << 15), 1)
|
| 357 |
+
ring_l2 = _sattolo_ring((2 << 20) // 4, device)
|
| 358 |
+
lat["l2_cycles"] = round(
|
| 359 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l2,
|
| 360 |
+
ring_l2.numel(), 1 << 14), 1)
|
| 361 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 362 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 363 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 364 |
+
lat["dram_cycles"] = round(
|
| 365 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_h,
|
| 366 |
+
1 << 14, hops_h), 1)
|
|
|
|
|
|
|
| 367 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 368 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 369 |
+
"Sattolo-cycle pointer chase, min of 3 runs; 16 "
|
| 370 |
+
"KB ring for L1, 2 MB TLB-resident ring for L2, "
|
| 371 |
+
"self-evicting chain (2x L2 in touched lines) "
|
| 372 |
+
"for HBM", idle_clk, "high",
|
| 373 |
"smem includes index arithmetic; HBM includes "
|
| 374 |
"residual TLB effects at large footprints")
|
| 375 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 376 |
|
| 377 |
+
# ---- fp32/fp64 FMA and tensor-core throughput
|
| 378 |
blocks = sm * 4
|
| 379 |
iters = 20_000 if quick else 60_000
|
| 380 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 381 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 382 |
reps))
|
| 383 |
+
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 384 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 385 |
"register-resident FMA chains", clk, "high")
|
| 386 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
|
|
| 392 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 393 |
"register-resident double FMA chains (the DFMA "
|
| 394 |
"pipe)", clk, "high")
|
| 395 |
+
|
| 396 |
+
# ---- ILP ladder: 1/2/4 chains at low occupancy separates latency-bound from pipe-bound
|
| 397 |
+
ilp = {}
|
| 398 |
+
it_ilp = 20_000 if quick else 60_000
|
| 399 |
+
for ch in (1, 2, 4):
|
| 400 |
+
ops.mk_fma_ilp(ch, sm, 64, it_ilp, sink)
|
| 401 |
+
ms = _median(_events(
|
| 402 |
+
lambda c2=ch: ops.mk_fma_ilp(c2, sm, 64, it_ilp, sink), reps))
|
| 403 |
+
ilp[f"ilp{ch}"] = round(
|
| 404 |
+
sm * 64 * it_ilp * 16 * 2 * ch / (ms * 1e-3) / 1e12, 2)
|
| 405 |
+
ilp["ratio_4_1"] = round(ilp["ilp4"] / ilp["ilp1"], 2)
|
| 406 |
+
d["fma_ilp"] = _entry(ilp, "TFLOP/s",
|
| 407 |
+
"1/2/4 independent dependent FMA chains per "
|
| 408 |
+
"thread at one block of 64 threads per SM; a "
|
| 409 |
+
"ratio near 4 means latency-bound at that "
|
| 410 |
+
"occupancy", clk, "high")
|
| 411 |
+
|
| 412 |
mma = {}
|
| 413 |
mma_klanes = {}
|
| 414 |
+
fp8_accum = {}
|
| 415 |
+
for name, (kind, mn, k_nom, nom_lanes) in _MMA_KINDS.items():
|
| 416 |
it = 30_000 if quick else 100_000
|
| 417 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 418 |
if r != 0:
|
| 419 |
mma[name] = "unsupported"
|
| 420 |
continue
|
| 421 |
_sync()
|
| 422 |
+
gain, linear = _mma_calibrate(kind, sink)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
mma_klanes[name] = {
|
| 424 |
+
"executed_k_per_iter": round(gain, 2),
|
| 425 |
+
"vs_nominal": round(gain / nom_lanes, 3),
|
| 426 |
+
"calibration_linear": linear,
|
| 427 |
}
|
| 428 |
+
if name.startswith("fp8"):
|
| 429 |
+
fp8_accum[name] = _fp8_accum_stall(kind, sink, k_nom)
|
| 430 |
ms = _median(_events(
|
| 431 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 432 |
+
tflops = blocks * 8 * it * 2.0 * mn * gain / (ms * 1e-3) / 1e12
|
| 433 |
+
if name == "fp16_sparse_2_4":
|
| 434 |
+
mma[name] = {"executed": round(tflops, 1),
|
| 435 |
+
"dense_equivalent": round(2 * tflops, 1)}
|
| 436 |
+
else:
|
| 437 |
+
mma[name] = round(tflops, 1)
|
| 438 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 439 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 440 |
+
"2:4-sparse fp16 via inline PTX m16n8k32); flops "
|
| 441 |
+
"derive from a sub-saturation accumulator read-back "
|
|
|
|
| 442 |
"with every fragment element observed, never the "
|
| 443 |
+
"declared loop shape; sparse reports executed and "
|
| 444 |
+
"dense-equivalent flops",
|
| 445 |
clk, "medium",
|
| 446 |
"dense-tile rate without memory traffic; real "
|
| 447 |
"kernels see this only when staging hides behind it")
|
| 448 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
| 449 |
+
if fp8_accum:
|
| 450 |
+
d["mma"]["fp8_accum"] = fp8_accum
|
| 451 |
|
| 452 |
+
# ---- fed MMA: smem-staged fp16 tiles under cp.async double buffering
|
| 453 |
+
if props.major >= 8:
|
| 454 |
+
gbuf = torch.ones(16 << 20, dtype=torch.float16, device=device)
|
| 455 |
+
sink[1] = 0.0
|
| 456 |
+
r = ops.mk_mma_fed(gbuf, 1, 1, 64, sink)
|
| 457 |
+
if r == 0:
|
| 458 |
+
_sync()
|
| 459 |
+
fed_gain = float(sink[1].item()) / 64
|
| 460 |
+
it_fed = 6_000 if quick else 20_000
|
| 461 |
+
ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink)
|
| 462 |
+
ms = _median(_events(
|
| 463 |
+
lambda: ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink),
|
| 464 |
+
reps))
|
| 465 |
+
fed = sm * 2 * 8 * it_fed * 2.0 * 256 * fed_gain / \
|
| 466 |
+
(ms * 1e-3) / 1e12
|
| 467 |
+
d["mma_fed"] = _entry(
|
| 468 |
+
round(fed, 1), "TFLOP/s",
|
| 469 |
+
"m16n16k16 fp16 tiles fed from an L2-resident buffer "
|
| 470 |
+
"through cp.async double-buffered shared memory; "
|
| 471 |
+
"accumulator-verified", clk, "high",
|
| 472 |
+
"the achieved counterpart to the register peak: staging "
|
| 473 |
+
"exposed, one dependent accumulator chain per warp")
|
| 474 |
+
del gbuf
|
| 475 |
+
|
| 476 |
+
# ---- shared-memory bandwidth and the bank-conflict stride sweep
|
| 477 |
it_sm = 30_000 if quick else 100_000
|
| 478 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 479 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
|
|
| 483 |
"block-resident read+write streaming (8 B per "
|
| 484 |
"thread-iteration), aggregated across SMs", clk,
|
| 485 |
"medium",
|
| 486 |
+
"bank-conflict-light access pattern; the stride "
|
| 487 |
+
"sweep below prices the conflicted case")
|
| 488 |
+
strides = (1, 8, 32) if quick else (1, 2, 4, 8, 16, 32)
|
| 489 |
+
it_st = 8_000 if quick else 20_000
|
| 490 |
+
st_ms = {}
|
| 491 |
+
for s2 in strides:
|
| 492 |
+
ops.mk_smem_stride(s2, blocks, 256, it_st, sink)
|
| 493 |
+
st_ms[s2] = _median(_events(
|
| 494 |
+
lambda s3=s2: ops.mk_smem_stride(s3, blocks, 256, it_st, sink),
|
| 495 |
+
reps))
|
| 496 |
+
d["smem_conflict_penalty"] = _entry(
|
| 497 |
+
{f"stride_{s2}": round(st_ms[s2] / st_ms[1], 2) for s2 in strides},
|
| 498 |
+
"x (time ratio vs stride 1)",
|
| 499 |
+
"dependent read-FMA-write per thread at warp-lane bank distance "
|
| 500 |
+
"`stride`; stride 32 puts every lane in one bank", clk, "high",
|
| 501 |
+
"the direct conflict measurement that disambiguates a "
|
| 502 |
+
"bank-conflict signature from an ILP stall")
|
| 503 |
|
| 504 |
# ---- PCIe / host transfer bandwidth
|
| 505 |
nbytes = 256 << 20
|
|
|
|
| 530 |
"d2d counts one direction of traffic")
|
| 531 |
del host_pin, host_pag, dev_t, dev_u
|
| 532 |
|
| 533 |
+
# ---- atomic contention curve
|
|
|
|
|
|
|
| 534 |
n_thr = blocks * 256
|
| 535 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 536 |
at = {"curve_slots": {}}
|
|
|
|
| 540 |
buf.zero_()
|
| 541 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 542 |
ms = _median(_events(
|
| 543 |
+
lambda s4=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s4),
|
| 544 |
reps))
|
| 545 |
at["curve_slots"][str(slots)] = round(
|
| 546 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
|
|
| 553 |
"of distinct global addresses from 1 (full "
|
| 554 |
"contention) to one per thread", clk, "high")
|
| 555 |
|
| 556 |
+
# ---- launch overhead: torch-dispatch path and the raw C++ path
|
| 557 |
anchor = sink
|
| 558 |
n_launch = 500 if quick else 2000
|
| 559 |
_sync()
|
|
|
|
| 568 |
ops.mk_empty(anchor)
|
| 569 |
_sync()
|
| 570 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
| 571 |
+
raw_q = float(ops.mk_launch_raw(n_launch, 0, anchor))
|
| 572 |
+
raw_rt = float(ops.mk_launch_raw(n_rt, 1, anchor))
|
| 573 |
d["launch_overhead"] = _entry(
|
| 574 |
+
{"torch_queued_us": round(queued, 2),
|
| 575 |
+
"torch_round_trip_us": round(rt, 2),
|
| 576 |
+
"raw_queued_us": round(raw_q / n_launch * 1e6, 2)
|
| 577 |
+
if raw_q >= 0 else None,
|
| 578 |
+
"raw_round_trip_us": round(raw_rt / n_rt * 1e6, 2)
|
| 579 |
+
if raw_rt >= 0 else None},
|
| 580 |
+
"us", "empty-kernel trains; torch path per-launch through op "
|
| 581 |
+
"dispatch, raw path looped inside one binding call", clk,
|
| 582 |
+
"medium", "host-state-dependent; the torch-minus-raw gap is the "
|
| 583 |
+
"dispatch cost a torch-launched candidate pays")
|
| 584 |
|
| 585 |
# ---- grid barrier
|
| 586 |
bar_iters = 500 if quick else 2000
|
|
|
|
| 596 |
"unavailable or grid too large", clk,
|
| 597 |
"high")
|
| 598 |
|
| 599 |
+
# ---- occupancy API table plus timed knees
|
|
|
|
|
|
|
| 600 |
occ = {}
|
| 601 |
for threads in (128, 256, 512, 1024):
|
| 602 |
row = {}
|
|
|
|
| 617 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 618 |
knee_iters, sink)
|
| 619 |
ms = _median(_events(
|
| 620 |
+
lambda t=threads, s5=smem_kb: ops.mk_fma_dep(
|
| 621 |
+
knee_blocks, t, s5 * 1024, knee_iters, sink), reps))
|
| 622 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 623 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 624 |
knees[f"t{threads}"] = row
|
|
|
|
| 627 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 628 |
"the cliff marks where occupancy stops covering latency",
|
| 629 |
clk, "high", "timing-derived counterpart to the API table above")
|
| 630 |
+
if cached:
|
| 631 |
+
import json
|
| 632 |
+
json.dump(d, open(cpath, "w", encoding="utf-8"))
|
| 633 |
return d
|
| 634 |
|
| 635 |
|
| 636 |
+
def probe_all_devices(quick=True, cached=True, tenancy_check=True):
|
| 637 |
+
"""One dossier per visible device plus a pairwise P2P bandwidth matrix."""
|
| 638 |
+
n = torch.cuda.device_count()
|
| 639 |
+
devices = {}
|
| 640 |
+
for i in range(n):
|
| 641 |
+
devices[i] = probe_device(f"cuda:{i}", quick=quick, cached=cached,
|
| 642 |
+
tenancy_check=tenancy_check)
|
| 643 |
+
p2p = {}
|
| 644 |
+
if n > 1:
|
| 645 |
+
nbytes = 64 << 20
|
| 646 |
+
for i in range(n):
|
| 647 |
+
for j in range(n):
|
| 648 |
+
if i == j:
|
| 649 |
+
continue
|
| 650 |
+
src = torch.rand(nbytes // 4, device=f"cuda:{i}")
|
| 651 |
+
dst = torch.empty(nbytes // 4, device=f"cuda:{j}")
|
| 652 |
+
dst.copy_(src)
|
| 653 |
+
torch.cuda.synchronize(i)
|
| 654 |
+
torch.cuda.synchronize(j)
|
| 655 |
+
t0 = time.perf_counter()
|
| 656 |
+
for _ in range(5):
|
| 657 |
+
dst.copy_(src)
|
| 658 |
+
torch.cuda.synchronize(i)
|
| 659 |
+
torch.cuda.synchronize(j)
|
| 660 |
+
ms = (time.perf_counter() - t0) / 5 * 1e3
|
| 661 |
+
p2p[f"{i}->{j}"] = {
|
| 662 |
+
"gbs": round(nbytes / (ms * 1e-3) / 1e9, 1),
|
| 663 |
+
"peer_access": bool(
|
| 664 |
+
torch.cuda.can_device_access_peer(i, j)),
|
| 665 |
+
}
|
| 666 |
+
del src, dst
|
| 667 |
+
return {"devices": devices, "p2p": p2p}
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
_CAPTURE_PROBE = r"""
|
| 671 |
+
import pickle, sys, torch
|
| 672 |
+
fn = pickle.load(open(sys.argv[1], "rb"))
|
| 673 |
+
for _ in range(3):
|
| 674 |
+
fn()
|
| 675 |
+
torch.cuda.synchronize()
|
| 676 |
+
try:
|
| 677 |
+
g = torch.cuda.CUDAGraph()
|
| 678 |
+
with torch.cuda.graph(g):
|
| 679 |
+
fn()
|
| 680 |
+
print("capturable", flush=True)
|
| 681 |
+
except Exception as e:
|
| 682 |
+
print("uncapturable: %s: %s" % (type(e).__name__, str(e)[:120]),
|
| 683 |
+
flush=True)
|
| 684 |
+
"""
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def probe_capturable(fn, timeout_s=180):
|
| 688 |
+
"""Test CUDA-graph capturability of fn in a child process, so a capture-wedged RNG never reaches the caller."""
|
| 689 |
+
import pickle
|
| 690 |
+
import subprocess
|
| 691 |
+
import sys as _sys
|
| 692 |
+
import tempfile
|
| 693 |
+
try:
|
| 694 |
+
blob = pickle.dumps(fn)
|
| 695 |
+
except Exception: # noqa: BLE001
|
| 696 |
+
return {"capturable": None,
|
| 697 |
+
"reason": "fn is not picklable; probe skipped (define fn "
|
| 698 |
+
"at module top level to enable it)"}
|
| 699 |
+
path = os.path.join(tempfile.mkdtemp(prefix="mk_cap_"), "fn.pkl")
|
| 700 |
+
with open(path, "wb") as f:
|
| 701 |
+
f.write(blob)
|
| 702 |
+
try:
|
| 703 |
+
r = subprocess.run([_sys.executable, "-c", _CAPTURE_PROBE, path],
|
| 704 |
+
capture_output=True, text=True,
|
| 705 |
+
timeout=timeout_s)
|
| 706 |
+
except Exception as e: # noqa: BLE001
|
| 707 |
+
return {"capturable": None,
|
| 708 |
+
"reason": f"probe child failed: {type(e).__name__}"}
|
| 709 |
+
out = (r.stdout or "").strip()
|
| 710 |
+
if out == "capturable":
|
| 711 |
+
return {"capturable": True, "reason": ""}
|
| 712 |
+
if out.startswith("uncapturable"):
|
| 713 |
+
return {"capturable": False, "reason": out}
|
| 714 |
+
return {"capturable": None,
|
| 715 |
+
"reason": "probe child exit %s: %s" % (
|
| 716 |
+
r.returncode, (r.stderr or "")[-200:])}
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
def _bench_verdict(rep):
|
| 720 |
+
parts = ["median %.4g ms" % rep["median_ms"]]
|
| 721 |
+
if "achieved_tflops" in rep:
|
| 722 |
+
parts.append("%.4g TFLOP/s" % rep["achieved_tflops"])
|
| 723 |
+
elif "achieved_gbs" in rep:
|
| 724 |
+
parts.append("%.4g GB/s" % rep["achieved_gbs"])
|
| 725 |
+
if "roofline" in rep:
|
| 726 |
+
parts.append("%.0f%% of %s roof" % (rep["roofline"]["pct_of_roof"],
|
| 727 |
+
rep["roofline"]["bound"]))
|
| 728 |
+
if rep["rejected_throttled"]:
|
| 729 |
+
parts.append("%d/%d rejected (%s)" % (
|
| 730 |
+
rep["rejected_throttled"], rep["samples"],
|
| 731 |
+
rep["rejection_basis"]))
|
| 732 |
+
if rep.get("no_clean_window"):
|
| 733 |
+
parts.append("no clean window: the whole run was throttled")
|
| 734 |
+
if rep.get("alloc_growth_bytes_per_call"):
|
| 735 |
+
parts.append("fn allocates %d B/call" %
|
| 736 |
+
rep["alloc_growth_bytes_per_call"])
|
| 737 |
+
if rep.get("host_overhead_suspected"):
|
| 738 |
+
parts.append("fn appears to sync or do host work inside")
|
| 739 |
+
return "; ".join(parts)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 743 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 744 |
+
graph_probe_isolated=False, device="cuda"):
|
| 745 |
+
"""Median-of-N cudaEvent timing with throttle rejection, roofline placement, and footgun detection."""
|
| 746 |
+
for name, v in (("iters", iters), ("warmup", warmup)):
|
| 747 |
+
if not isinstance(v, int) or v < (1 if name == "iters" else 0):
|
| 748 |
+
raise ValueError(f"{name} must be a non-negative int; "
|
| 749 |
+
f"got {v!r}")
|
| 750 |
+
for name, v in (("bytes", bytes), ("flops", flops)):
|
| 751 |
+
if v is not None and (not isinstance(v, (int, float)) or v <= 0):
|
| 752 |
+
raise ValueError(f"{name} must be a positive number counting "
|
| 753 |
+
f"{name} per call of fn; got {v!r}")
|
|
|
|
| 754 |
for _ in range(warmup):
|
| 755 |
fn()
|
| 756 |
_sync()
|
| 757 |
+
# footgun probes: per-call allocation growth and hidden host work
|
| 758 |
+
alloc0 = torch.cuda.memory_allocated(device)
|
| 759 |
+
fn()
|
| 760 |
+
fn()
|
| 761 |
+
_sync()
|
| 762 |
+
alloc_growth = max(0, (torch.cuda.memory_allocated(device) - alloc0)
|
| 763 |
+
// 2)
|
| 764 |
+
# a burst of enqueues that leaves the stream idle means fn drains it; only trustworthy above the launch-overhead floor
|
| 765 |
+
for _ in range(16):
|
| 766 |
+
fn()
|
| 767 |
+
stream_idle = bool(torch.cuda.current_stream(device).query())
|
| 768 |
+
_sync()
|
| 769 |
+
t0 = time.perf_counter()
|
| 770 |
+
pre = _events(fn, 4)
|
| 771 |
+
wall_ms = (time.perf_counter() - t0) * 1e3
|
| 772 |
+
ev_ms = sum(pre)
|
| 773 |
+
host_suspect = (stream_idle and ev_ms / 4 > 0.05) or \
|
| 774 |
+
wall_ms > ev_ms * 1.5 + 2.0
|
| 775 |
+
|
| 776 |
graph_state = "off"
|
| 777 |
graph_reason = ""
|
| 778 |
+
if graph and graph_probe_isolated:
|
| 779 |
+
cap = probe_capturable(fn)
|
| 780 |
+
if cap["capturable"] is False:
|
| 781 |
+
graph = False
|
| 782 |
+
graph_state = "fallback_eager"
|
| 783 |
+
graph_reason = "isolated probe: " + cap["reason"]
|
| 784 |
if graph:
|
| 785 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 786 |
try:
|
|
|
|
| 795 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 796 |
graph_state = "fallback_eager"
|
| 797 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 798 |
+
# drain the invalidated stream; a failed capture can wedge the device RNG on some torch versions
|
|
|
|
|
|
|
|
|
|
| 799 |
rng_ok = True
|
| 800 |
for _attempt in range(5):
|
| 801 |
try:
|
|
|
|
| 825 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 826 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 827 |
best_clk = max(clocks)
|
| 828 |
+
|
| 829 |
+
# rejection basis: memory-bound work is judged on its own rate, not the SM clock
|
| 830 |
+
ridge_memory = False
|
| 831 |
+
if dossier is not None and bytes and flops:
|
| 832 |
+
bw0 = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 833 |
+
mma_v0 = dossier["mma"]["value"].get(dtype)
|
| 834 |
+
if isinstance(mma_v0, dict):
|
| 835 |
+
mma_v0 = mma_v0.get("dense_equivalent")
|
| 836 |
+
peak0 = (mma_v0 if isinstance(mma_v0, (int, float))
|
| 837 |
+
else dossier["fma_f32"]["value"]) * 1e12
|
| 838 |
+
ridge_memory = (flops / bytes) * bw0 < peak0
|
| 839 |
+
basis = "bandwidth_consistency" if (
|
| 840 |
+
bytes is not None and (flops is None or ridge_memory)) \
|
| 841 |
+
else "sm_clock"
|
| 842 |
+
|
| 843 |
flat = []
|
| 844 |
+
kept = []
|
| 845 |
+
rejected = 0
|
| 846 |
+
if basis == "sm_clock":
|
| 847 |
+
for group, ci in samples:
|
| 848 |
+
window_ok = (clocks[ci] >= clock_guard * best_clk and
|
| 849 |
+
clocks[min(ci + 1, len(clocks) - 1)] >=
|
| 850 |
+
clock_guard * best_clk)
|
| 851 |
+
for ms in group:
|
| 852 |
+
flat.append(ms)
|
| 853 |
+
if window_ok:
|
| 854 |
+
kept.append(ms)
|
| 855 |
+
else:
|
| 856 |
+
rejected += 1
|
| 857 |
+
else:
|
| 858 |
+
for group, _ci in samples:
|
| 859 |
+
flat.extend(group)
|
| 860 |
+
floor = sorted(flat)[max(0, int(0.05 * len(flat)) - 1)]
|
| 861 |
+
for ms in flat:
|
| 862 |
+
if ms <= floor * 1.15:
|
| 863 |
kept.append(ms)
|
| 864 |
else:
|
| 865 |
rejected += 1
|
| 866 |
+
all_rejected = not kept
|
| 867 |
+
if all_rejected:
|
| 868 |
kept = flat
|
| 869 |
med = _median(kept)
|
| 870 |
ks = sorted(kept)
|
| 871 |
n_k = len(ks)
|
| 872 |
+
# order-statistic 95% CI on the median; degenerate at tiny n
|
|
|
|
| 873 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 874 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 875 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
|
|
| 884 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 885 |
"samples": len(flat),
|
| 886 |
"rejected_throttled": rejected,
|
| 887 |
+
"rejection_basis": basis,
|
| 888 |
"graph_captured": graph_state == "captured",
|
| 889 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 890 |
"worst": round(min(clocks), 3)},
|
| 891 |
}
|
| 892 |
+
no_clean = all_rejected
|
| 893 |
+
if basis == "sm_clock" and dossier is not None:
|
| 894 |
+
ref_clk = dossier["clock"]["max_ghz"]
|
| 895 |
+
if best_clk < clock_guard * ref_clk:
|
| 896 |
+
no_clean = True
|
| 897 |
+
if no_clean:
|
| 898 |
+
rep["no_clean_window"] = True
|
| 899 |
if graph_reason:
|
| 900 |
rep["graph_fallback_reason"] = graph_reason
|
| 901 |
+
if alloc_growth:
|
| 902 |
+
rep["alloc_growth_bytes_per_call"] = int(alloc_growth)
|
| 903 |
+
if host_suspect:
|
| 904 |
+
rep["host_overhead_suspected"] = True
|
| 905 |
+
rep["host_wall_over_event"] = round(wall_ms / max(ev_ms, 1e-9), 2)
|
| 906 |
if bytes is not None:
|
| 907 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 908 |
if flops is not None:
|
|
|
|
| 912 |
if dossier is not None and bytes and flops:
|
| 913 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 914 |
mma_v = dossier["mma"]["value"].get(dtype)
|
| 915 |
+
if isinstance(mma_v, dict):
|
| 916 |
+
mma_v = mma_v.get("dense_equivalent")
|
| 917 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 918 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 919 |
ai = flops / bytes
|
|
|
|
| 924 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 925 |
"peak_dtype": dtype,
|
| 926 |
}
|
| 927 |
+
rep["verdict"] = _bench_verdict(rep)
|
| 928 |
return rep
|
| 929 |
|
| 930 |
|
| 931 |
def _to_ordered_int(t):
|
| 932 |
+
"""Monotone integer mapping of float bit patterns; differences are ULP distances."""
|
|
|
|
|
|
|
| 933 |
if t.dtype == torch.float32:
|
| 934 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 935 |
half = 1 << 31
|
|
|
|
| 945 |
|
| 946 |
|
| 947 |
def ulp_diff(a, b):
|
| 948 |
+
"""Elementwise ULP distance; both-NaN counts 0, one-sided NaN 2**62, integers by absolute difference."""
|
| 949 |
+
if a.dtype != b.dtype or a.shape != b.shape:
|
| 950 |
+
raise ValueError("ulp_diff needs matching dtype and shape; got "
|
| 951 |
+
f"{a.dtype}{tuple(a.shape)} vs "
|
| 952 |
+
f"{b.dtype}{tuple(b.shape)}; cast or reshape the "
|
| 953 |
+
"candidate to the reference layout")
|
| 954 |
if not a.is_floating_point():
|
| 955 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 956 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
|
|
| 967 |
|
| 968 |
@staticmethod
|
| 969 |
def last_dim():
|
| 970 |
+
"""Identical permutation of every arg's last dim: reduction order changes, value does not."""
|
|
|
|
|
|
|
| 971 |
def shuffle(args, seed):
|
| 972 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 973 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
|
|
| 977 |
|
| 978 |
@staticmethod
|
| 979 |
def matmul_k():
|
| 980 |
+
"""Consistent K permutation of (A [.., M, K], B [.., K, N]): product unchanged, accumulation order not."""
|
|
|
|
| 981 |
def shuffle(args, seed):
|
| 982 |
a_m, b_m = args[0], args[1]
|
| 983 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
|
|
| 991 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 992 |
|
| 993 |
|
| 994 |
+
def _compare_hints(trial):
|
| 995 |
+
hints = []
|
| 996 |
+
if trial.get("bias_one_sided_frac", 0) >= 0.9 and \
|
| 997 |
+
trial.get("frac_above_band", 0) > 0.5:
|
| 998 |
+
hints.append("systematic one-sided bias: check scale factors or "
|
| 999 |
+
"the accumulator dtype")
|
| 1000 |
+
if trial["nan_cand"] > trial["nan_ref"]:
|
| 1001 |
+
hints.append("candidate introduces NaN: check uninitialized "
|
| 1002 |
+
"memory or masking")
|
| 1003 |
+
nd = trial.get("first_divergent_nd")
|
| 1004 |
+
if nd and nd[-1] > 0 and any(nd[-1] % t == 0 for t in (16, 32, 64,
|
| 1005 |
+
128)):
|
| 1006 |
+
hints.append("divergence starts on a tile-aligned index: check "
|
| 1007 |
+
"boundary indexing")
|
| 1008 |
+
return hints
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def compare(ref, cand, gen=None, trials=8, order_shuffle=None,
|
| 1012 |
+
order_trials=8, ref_variants=None, ulp_tol=None, atol=None,
|
| 1013 |
+
rtol=None, band_margin=4.0, zero_atol=None, worst_k=8, seed=0,
|
| 1014 |
+
inputs=None, frac_above_tol=0.5):
|
| 1015 |
+
"""Adversarial correctness comparison with pointwise order bands, ULP evidence, and a one-sided-bias gate; the banded route cannot reject errors under band_margin*(band+2) ULP at order-stable elements."""
|
| 1016 |
+
if (gen is None) == (inputs is None):
|
| 1017 |
+
raise ValueError("pass exactly one of gen (a seed -> args "
|
| 1018 |
+
"callable) or inputs (a tuple of concrete "
|
| 1019 |
+
"tensors)")
|
| 1020 |
+
if inputs is not None:
|
| 1021 |
+
if isinstance(inputs, torch.Tensor):
|
| 1022 |
+
inputs = (inputs,)
|
| 1023 |
+
if not all(isinstance(x, torch.Tensor) for x in inputs):
|
| 1024 |
+
raise ValueError("inputs must be tensors; wrap scalars with "
|
| 1025 |
+
"torch.tensor first")
|
| 1026 |
+
fixed = tuple(inputs)
|
| 1027 |
+
gen = lambda seed: fixed # noqa: E731
|
| 1028 |
+
trials = 1
|
| 1029 |
worst = {"max_ulp": -1}
|
| 1030 |
total_hist = {}
|
| 1031 |
per_trial = []
|
| 1032 |
max_abs_seen = 0.0
|
| 1033 |
max_rel_seen = 0.0
|
| 1034 |
+
out_shapes = []
|
| 1035 |
for t in range(trials):
|
| 1036 |
args = gen(seed + t)
|
| 1037 |
+
if not isinstance(args, (tuple, list)):
|
| 1038 |
+
raise ValueError("gen(seed) must return a tuple of args; got "
|
| 1039 |
+
f"{type(args).__name__}; return (x,) for a "
|
| 1040 |
+
"single-argument op")
|
| 1041 |
r_outs = _as_outputs(ref(*args))
|
| 1042 |
c_outs = _as_outputs(cand(*args))
|
| 1043 |
+
if len(r_outs) != len(c_outs):
|
| 1044 |
+
raise ValueError(f"reference returned {len(r_outs)} outputs, "
|
| 1045 |
+
f"candidate {len(c_outs)}; make the arities "
|
| 1046 |
+
"match")
|
| 1047 |
+
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
| 1048 |
+
if r.dtype != c.dtype:
|
| 1049 |
+
raise ValueError(f"output {oi} dtype mismatch: ref "
|
| 1050 |
+
f"{r.dtype}, cand {c.dtype}; cast the "
|
| 1051 |
+
"candidate to the reference dtype")
|
| 1052 |
+
if r.shape != c.shape:
|
| 1053 |
+
raise ValueError(f"output {oi} shape mismatch: ref "
|
| 1054 |
+
f"{tuple(r.shape)}, cand "
|
| 1055 |
+
f"{tuple(c.shape)}")
|
| 1056 |
+
out_shapes = [tuple(r.shape) for r in r_outs]
|
| 1057 |
+
# pointwise band: each element judged against its own measured order sensitivity
|
| 1058 |
bands = None
|
| 1059 |
band = 0
|
| 1060 |
band_score = 0.0
|
|
|
|
| 1078 |
max_ulp = 0
|
| 1079 |
mean_ulp = 0.0
|
| 1080 |
first_div = -1
|
| 1081 |
+
first_div_nd = None
|
| 1082 |
n_el = 0
|
| 1083 |
n_above_band = 0
|
| 1084 |
+
n_diverging = 0
|
| 1085 |
+
n_cand_high = 0
|
| 1086 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 1087 |
worst_elements = []
|
| 1088 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
|
|
| 1100 |
mean_ulp += float(d.double().sum().item())
|
| 1101 |
n_el += d.numel()
|
| 1102 |
nz = (d > 0)
|
| 1103 |
+
n_diverging += int(nz.sum().item())
|
| 1104 |
+
n_cand_high += int(((c > r) & nz).sum().item())
|
| 1105 |
if first_div < 0 and nz.any():
|
| 1106 |
first_div = int(nz.flatten().nonzero()[0].item())
|
| 1107 |
+
first_div_nd = _unravel(first_div, r.shape)
|
| 1108 |
if nz.any():
|
| 1109 |
buckets = torch.clamp(
|
| 1110 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
|
|
| 1121 |
if val > 0:
|
| 1122 |
worst_elements.append({
|
| 1123 |
"output": oi, "index": int(ix),
|
| 1124 |
+
"index_nd": _unravel(int(ix), r.shape),
|
| 1125 |
"ref": float(rf[ix].item()),
|
| 1126 |
"cand": float(cf[ix].item()),
|
| 1127 |
"ulp": int(val)})
|
|
|
|
| 1141 |
inf_r += int(torch.isinf(r).sum().item())
|
| 1142 |
inf_c += int(torch.isinf(c).sum().item())
|
| 1143 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
| 1144 |
+
frac_pos = n_cand_high / max(n_diverging, 1)
|
| 1145 |
trial_rep = {
|
| 1146 |
"max_ulp": max_ulp,
|
| 1147 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 1148 |
"first_divergent_index": first_div,
|
| 1149 |
+
"first_divergent_nd": first_div_nd,
|
| 1150 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 1151 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 1152 |
"order_band_ulp": band,
|
| 1153 |
"band_score": round(band_score, 2),
|
| 1154 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
| 1155 |
+
"bias_one_sided_frac": round(max(frac_pos, 1 - frac_pos), 4)
|
| 1156 |
+
if n_diverging else 0.0,
|
| 1157 |
"worst_elements": worst_elements[:worst_k],
|
| 1158 |
}
|
| 1159 |
per_trial.append(trial_rep)
|
| 1160 |
if max_ulp > worst["max_ulp"]:
|
| 1161 |
worst = trial_rep
|
|
|
|
| 1162 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 1163 |
+
banded = order_shuffle is not None or ref_variants
|
| 1164 |
+
if banded:
|
| 1165 |
score = max(t["band_score"] for t in per_trial)
|
| 1166 |
ulp_ok = score <= band_margin
|
| 1167 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
|
|
| 1176 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 1177 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 1178 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 1179 |
+
# one-sided bias gate: a uniform small error is above-band nearly everywhere in one direction
|
| 1180 |
+
bias_fail = False
|
| 1181 |
+
if banded and (ulp_ok or tol_ok):
|
| 1182 |
+
w_frac = max(t["frac_above_band"] for t in per_trial)
|
| 1183 |
+
w_side = max(t["bias_one_sided_frac"] for t in per_trial)
|
| 1184 |
+
if w_frac > frac_above_tol and w_side >= 0.9:
|
| 1185 |
+
bias_fail = True
|
| 1186 |
+
criterion += ("; FAILED bias gate: frac_above_band %.3f > %s "
|
| 1187 |
+
"with one-sided fraction %.2f" %
|
| 1188 |
+
(w_frac, frac_above_tol, w_side))
|
| 1189 |
+
passed = bool((ulp_ok or tol_ok) and not bias_fail)
|
| 1190 |
+
hints = [] if passed else _compare_hints(worst)
|
| 1191 |
+
if passed:
|
| 1192 |
+
verdict = "pass: " + (
|
| 1193 |
+
f"max {max_seen} ULP within the order band" if banded
|
| 1194 |
+
else f"max {max_seen} ULP")
|
| 1195 |
+
elif bias_fail:
|
| 1196 |
+
verdict = ("fail: uniform bias, %.0f%% of elements above their "
|
| 1197 |
+
"order band in one direction" %
|
| 1198 |
+
(100 * max(t["frac_above_band"] for t in per_trial)))
|
| 1199 |
+
else:
|
| 1200 |
+
w0 = worst["worst_elements"][0] if worst["worst_elements"] else None
|
| 1201 |
+
verdict = "fail: max %d ULP" % max_seen + (
|
| 1202 |
+
" at index %s (ref %.6g, cand %.6g)" %
|
| 1203 |
+
(w0["index_nd"], w0["ref"], w0["cand"]) if w0 else "")
|
| 1204 |
+
rep = {
|
| 1205 |
+
"pass": passed,
|
| 1206 |
+
"verdict": verdict,
|
| 1207 |
"criterion": criterion,
|
| 1208 |
"worst_trial": worst,
|
| 1209 |
"max_abs": max_abs_seen,
|
|
|
|
| 1211 |
"trials": per_trial,
|
| 1212 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 1213 |
}
|
| 1214 |
+
if hints:
|
| 1215 |
+
rep["hints"] = hints
|
| 1216 |
+
return rep
|
| 1217 |
|
| 1218 |
|
| 1219 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
|
|
| 1229 |
return False
|
| 1230 |
|
| 1231 |
|
| 1232 |
+
class _RandnMaker:
|
| 1233 |
+
"""Picklable default maker: `arity` same-shape randn tensors (zeros for empty shapes)."""
|
| 1234 |
+
|
| 1235 |
+
def __init__(self, arity=1):
|
| 1236 |
+
self.arity = arity
|
| 1237 |
+
|
| 1238 |
+
def __call__(self, shape, dtype=torch.float32):
|
| 1239 |
+
n = math.prod(shape)
|
| 1240 |
+
if n:
|
| 1241 |
+
out = tuple(torch.randn(tuple(shape), device="cuda",
|
| 1242 |
+
dtype=dtype)
|
| 1243 |
+
for _ in range(self.arity))
|
| 1244 |
+
else:
|
| 1245 |
+
out = tuple(torch.zeros(tuple(shape), device="cuda",
|
| 1246 |
+
dtype=dtype)
|
| 1247 |
+
for _ in range(self.arity))
|
| 1248 |
+
return out if self.arity > 1 else out[0]
|
| 1249 |
+
|
| 1250 |
+
|
| 1251 |
+
def _default_make(arity=1):
|
| 1252 |
+
"""Default maker instance; a class so isolate=True can transport it."""
|
| 1253 |
+
return _RandnMaker(arity)
|
| 1254 |
+
|
| 1255 |
+
|
| 1256 |
def _fuzz_build(make, shape, dtype):
|
| 1257 |
try:
|
| 1258 |
out = make(tuple(shape), dtype)
|
|
|
|
| 1269 |
if v == "strided_rows":
|
| 1270 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 1271 |
if v == "offset_slice":
|
| 1272 |
+
# storage-offset-1 view: misaligned for vectorized loads
|
| 1273 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 1274 |
if v == "overflow_expand":
|
| 1275 |
if x.numel() == 0:
|
|
|
|
| 1283 |
|
| 1284 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 1285 |
stop_on_context_loss=True):
|
| 1286 |
+
"""All variant cases for one (shape, dtype) group; returns (rows, context_lost)."""
|
|
|
|
| 1287 |
rows = []
|
| 1288 |
bases = _fuzz_build(make, shape, dtype)
|
| 1289 |
lost = False
|
|
|
|
| 1320 |
got = op(*xs)
|
| 1321 |
want = ref(*xs)
|
| 1322 |
if v == "overflow_expand":
|
| 1323 |
+
# bounded comparison: head plus the high-index tail where int32 indexing wraps
|
|
|
|
| 1324 |
w = 1 << 20
|
| 1325 |
gf, wf = got.flatten(), want.flatten()
|
| 1326 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
|
|
| 1345 |
return rows, lost
|
| 1346 |
|
| 1347 |
|
| 1348 |
+
def _fn_spec(fn, name):
|
| 1349 |
+
"""Picklable transport spec for a callable, with a source fallback for local defs."""
|
| 1350 |
+
import pickle
|
| 1351 |
+
try:
|
| 1352 |
+
return ("pickle", pickle.dumps(fn), None)
|
| 1353 |
+
except Exception: # noqa: BLE001
|
| 1354 |
+
import inspect
|
| 1355 |
+
import textwrap
|
| 1356 |
+
try:
|
| 1357 |
+
src = textwrap.dedent(inspect.getsource(fn))
|
| 1358 |
+
except (OSError, TypeError):
|
| 1359 |
+
src = ""
|
| 1360 |
+
if not src.startswith("def "):
|
| 1361 |
+
raise ValueError(
|
| 1362 |
+
f"{name} is not picklable and not a plain def; define "
|
| 1363 |
+
"op/ref/make as top-level functions (not lambdas or "
|
| 1364 |
+
"closures) to use isolate=True") from None
|
| 1365 |
+
return ("source", src, fn.__name__)
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
_POOL_BOOTSTRAP = r"""
|
| 1369 |
+
import json, os, pickle, sys
|
| 1370 |
meta = json.load(open(sys.argv[1], "r"))
|
| 1371 |
+
sys.path[:0] = meta["sys_path"]
|
| 1372 |
try:
|
| 1373 |
import kernels
|
| 1374 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
|
|
| 1381 |
import load_local
|
| 1382 |
_mk = load_local.load()
|
| 1383 |
import torch
|
| 1384 |
+
_ns = {"torch": torch}
|
| 1385 |
+
def _resolve(spec):
|
| 1386 |
+
kind, payload, name = spec
|
| 1387 |
+
if kind == "pickle":
|
| 1388 |
+
return pickle.loads(payload)
|
| 1389 |
+
exec(payload, _ns)
|
| 1390 |
+
return _ns[name]
|
| 1391 |
+
_op, _ref, _make = (_resolve(s) for s in
|
| 1392 |
+
pickle.load(open(meta["fns"], "rb")))
|
| 1393 |
+
print("ready", flush=True)
|
| 1394 |
+
for line in sys.stdin:
|
| 1395 |
+
job = json.loads(line)
|
| 1396 |
+
rows, _l = _mk._fuzz_group(
|
| 1397 |
+
_op, _ref, _make, tuple(job["shape"]),
|
| 1398 |
+
getattr(torch, job["dtype"]), tuple(job["variants"]),
|
| 1399 |
+
job["ulp_tol"], job["device"], stop_on_context_loss=False)
|
| 1400 |
+
pickle.dump(rows, open(job["out"], "wb"))
|
| 1401 |
+
print("done", flush=True)
|
| 1402 |
"""
|
| 1403 |
|
| 1404 |
|
| 1405 |
+
class _FuzzPool:
|
| 1406 |
+
"""One persistent isolation worker fed groups over stdin, respawned only after a crash."""
|
| 1407 |
+
|
| 1408 |
+
def __init__(self, meta_path, stderr_path, timeout_s):
|
| 1409 |
+
self.meta_path = meta_path
|
| 1410 |
+
self.stderr_path = stderr_path
|
| 1411 |
+
self.timeout_s = timeout_s
|
| 1412 |
+
self.proc = None
|
| 1413 |
+
self.q = None
|
| 1414 |
+
|
| 1415 |
+
def _spawn(self):
|
| 1416 |
+
import queue
|
| 1417 |
+
import subprocess
|
| 1418 |
+
import sys as _sys
|
| 1419 |
+
import threading
|
| 1420 |
+
self.errf = open(self.stderr_path, "ab")
|
| 1421 |
+
self.proc = subprocess.Popen(
|
| 1422 |
+
[_sys.executable, "-c", _POOL_BOOTSTRAP, self.meta_path],
|
| 1423 |
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
| 1424 |
+
stderr=self.errf, text=True)
|
| 1425 |
+
self.q = queue.Queue()
|
| 1426 |
+
|
| 1427 |
+
def reader(p, q):
|
| 1428 |
+
for line in p.stdout:
|
| 1429 |
+
q.put(line.strip())
|
| 1430 |
+
threading.Thread(target=reader, args=(self.proc, self.q),
|
| 1431 |
+
daemon=True).start()
|
| 1432 |
+
try:
|
| 1433 |
+
first = self.q.get(timeout=self.timeout_s)
|
| 1434 |
+
except Exception: # noqa: BLE001
|
| 1435 |
+
first = None
|
| 1436 |
+
return first == "ready"
|
| 1437 |
+
|
| 1438 |
+
def run(self, job_line):
|
| 1439 |
+
"""Returns True when the worker reports done, False on crash or timeout."""
|
| 1440 |
+
import queue
|
| 1441 |
+
if self.proc is None or self.proc.poll() is not None:
|
| 1442 |
+
if not self._spawn():
|
| 1443 |
+
return False
|
| 1444 |
+
try:
|
| 1445 |
+
self.proc.stdin.write(job_line + "\n")
|
| 1446 |
+
self.proc.stdin.flush()
|
| 1447 |
+
except OSError:
|
| 1448 |
+
return False
|
| 1449 |
+
try:
|
| 1450 |
+
return self.q.get(timeout=self.timeout_s) == "done"
|
| 1451 |
+
except queue.Empty:
|
| 1452 |
+
if self.proc.poll() is None:
|
| 1453 |
+
self.proc.kill()
|
| 1454 |
+
return False
|
| 1455 |
+
|
| 1456 |
+
def stderr_tail(self):
|
| 1457 |
+
try:
|
| 1458 |
+
self.errf.flush()
|
| 1459 |
+
with open(self.stderr_path, "rb") as f:
|
| 1460 |
+
return f.read()[-400:].decode(errors="replace")
|
| 1461 |
+
except OSError:
|
| 1462 |
+
return ""
|
| 1463 |
+
|
| 1464 |
+
def close(self):
|
| 1465 |
+
if self.proc is not None and self.proc.poll() is None:
|
| 1466 |
+
self.proc.kill()
|
| 1467 |
+
try:
|
| 1468 |
+
self.errf.close()
|
| 1469 |
+
except Exception: # noqa: BLE001
|
| 1470 |
+
pass
|
| 1471 |
+
|
| 1472 |
+
|
| 1473 |
+
def _fuzz_hints(rows):
|
| 1474 |
+
hints = []
|
| 1475 |
+
bad = [c for c in rows if c["status"] == "mismatch"]
|
| 1476 |
+
if bad and all(c["variant"] in ("transposed", "strided_rows",
|
| 1477 |
+
"offset_slice") for c in bad):
|
| 1478 |
+
hints.append("mismatches only on non-contiguous variants: the op "
|
| 1479 |
+
"assumes contiguity; call .contiguous() or honor "
|
| 1480 |
+
"strides")
|
| 1481 |
+
if any(c["variant"] == "overflow_expand" and
|
| 1482 |
+
c["status"] == "mismatch" for c in rows):
|
| 1483 |
+
hints.append("overflow_expand mismatch: int32 indexing wraps past "
|
| 1484 |
+
"2^31 elements; use 64-bit index arithmetic")
|
| 1485 |
+
return hints
|
| 1486 |
+
|
| 1487 |
+
|
| 1488 |
+
def fuzz_shapes(op, ref, make=None, shapes=None, variants=_FUZZ_VARIANTS,
|
| 1489 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 1490 |
+
isolate_timeout_s=180, device="cuda", arity=1):
|
| 1491 |
+
"""Edge shapes, dtypes, and layout mutations with per-case repro recipes; isolate=True survives context-killing candidates via a pooled worker."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1492 |
if shapes is None:
|
| 1493 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1494 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
| 1495 |
+
if make is None:
|
| 1496 |
+
make = _default_make(arity)
|
| 1497 |
|
| 1498 |
rows = []
|
| 1499 |
lost = False
|
| 1500 |
if isolate:
|
| 1501 |
import json
|
| 1502 |
import pickle
|
|
|
|
| 1503 |
import sys as _sys
|
| 1504 |
import tempfile
|
| 1505 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1506 |
fns = os.path.join(tmpdir, "fns.pkl")
|
| 1507 |
+
specs = [_fn_spec(f, n) for f, n in ((op, "op"), (ref, "ref"),
|
| 1508 |
+
(make, "make"))]
|
| 1509 |
with open(fns, "wb") as f:
|
| 1510 |
+
pickle.dump(specs, f)
|
| 1511 |
+
meta_path = os.path.join(tmpdir, "meta.json")
|
| 1512 |
+
with open(meta_path, "w", encoding="utf-8") as f:
|
| 1513 |
+
json.dump({"sys_path": [p for p in _sys.path
|
| 1514 |
+
if isinstance(p, str)],
|
| 1515 |
+
"repo": _REPO_ID, "version": _REPO_VERSION,
|
| 1516 |
+
"fns": fns}, f)
|
| 1517 |
+
pool = _FuzzPool(meta_path, os.path.join(tmpdir, "stderr.log"),
|
| 1518 |
+
isolate_timeout_s)
|
| 1519 |
+
try:
|
| 1520 |
+
for dtype in dtypes:
|
| 1521 |
+
for shape in shapes:
|
| 1522 |
+
out = os.path.join(tmpdir, "rows.pkl")
|
| 1523 |
+
if os.path.exists(out):
|
| 1524 |
+
os.remove(out)
|
| 1525 |
+
job = json.dumps({
|
| 1526 |
"shape": list(shape),
|
| 1527 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1528 |
"variants": list(variants),
|
| 1529 |
"ulp_tol": ulp_tol, "device": device,
|
| 1530 |
+
"out": out})
|
| 1531 |
+
ok = pool.run(job)
|
| 1532 |
+
if ok and os.path.exists(out):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
with open(out, "rb") as f:
|
| 1534 |
+
rows.extend(pickle.load(f))
|
| 1535 |
+
else:
|
| 1536 |
+
rows.append({
|
| 1537 |
+
"shape": tuple(shape), "variant": "*",
|
| 1538 |
+
"dtype": str(dtype).replace("torch.", ""),
|
| 1539 |
+
"status": "crashed_isolated",
|
| 1540 |
+
"stderr_tail": pool.stderr_tail()})
|
| 1541 |
+
finally:
|
| 1542 |
+
pool.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1543 |
else:
|
| 1544 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1545 |
for gi, (dtype, shape) in enumerate(pending):
|
|
|
|
| 1556 |
n_bad = sum(1 for r in rows
|
| 1557 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1558 |
"skipped_empty"))
|
| 1559 |
+
rep = {"cases": rows, "failed": n_bad, "total": len(rows),
|
| 1560 |
+
"context_lost": lost}
|
| 1561 |
+
hints = _fuzz_hints(rows)
|
| 1562 |
+
if hints:
|
| 1563 |
+
rep["hints"] = hints
|
| 1564 |
+
if n_bad == 0:
|
| 1565 |
+
rep["verdict"] = "pass: %d/%d cases ok" % (len(rows) - n_bad,
|
| 1566 |
+
len(rows))
|
| 1567 |
+
else:
|
| 1568 |
+
worst_case = next(c for c in rows
|
| 1569 |
+
if c["status"] not in ("ok", "skipped_vram",
|
| 1570 |
+
"skipped_empty"))
|
| 1571 |
+
rep["verdict"] = "fail: %d/%d cases (%s at %s %s)" % (
|
| 1572 |
+
n_bad, len(rows), worst_case["status"], worst_case["shape"],
|
| 1573 |
+
worst_case["variant"])
|
| 1574 |
+
return rep
|
| 1575 |
|
| 1576 |
|
| 1577 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1578 |
check=None, budget_s=None, race=False):
|
| 1579 |
+
"""Launch-configuration search, fastest first, with a correctness gate per row and no silently dropped points."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1580 |
keys = list(grid.keys())
|
| 1581 |
|
| 1582 |
def expand(i, acc):
|
|
|
|
| 1641 |
continue
|
| 1642 |
rows.append(run_point(p, iters))
|
| 1643 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
| 1644 |
+
if rows:
|
| 1645 |
+
n_pruned = sum(1 for r in rows if r.get("status") == "pruned_race")
|
| 1646 |
+
n_skip = sum(1 for r in rows if r.get("status") == "not_run")
|
| 1647 |
+
best = rows[0]
|
| 1648 |
+
rows_verdict = "fastest %s at %.4g ms" % (
|
| 1649 |
+
{k: best[k] for k in keys}, best["median_ms"])
|
| 1650 |
+
if n_pruned:
|
| 1651 |
+
rows_verdict += "; %d pruned" % n_pruned
|
| 1652 |
+
if n_skip:
|
| 1653 |
+
rows_verdict += "; %d not run" % n_skip
|
| 1654 |
+
best["verdict"] = rows_verdict
|
| 1655 |
return rows
|
| 1656 |
|
| 1657 |
|
| 1658 |
+
def judge(cand, ref, inputs=None, gen=None, bytes=None, flops=None,
|
| 1659 |
+
dtype=None, order_shuffle=None, make=None, quick_fuzz=True,
|
| 1660 |
+
device="cuda"):
|
| 1661 |
+
"""One-call verdict: compare, a quick fuzz, and a roofline bench against the cached dossier."""
|
| 1662 |
+
if (gen is None) == (inputs is None):
|
| 1663 |
+
raise ValueError("pass exactly one of inputs (concrete tensors) "
|
| 1664 |
+
"or gen (seed -> args)")
|
| 1665 |
+
if inputs is not None and isinstance(inputs, torch.Tensor):
|
| 1666 |
+
inputs = (inputs,)
|
| 1667 |
+
args0 = tuple(inputs) if inputs is not None else gen(0)
|
| 1668 |
+
arity = len(args0)
|
| 1669 |
+
if dtype is None:
|
| 1670 |
+
dt0 = args0[0].dtype
|
| 1671 |
+
dtype = {torch.float16: "fp16", torch.bfloat16: "bf16"}.get(
|
| 1672 |
+
dt0, "f32")
|
| 1673 |
+
dossier = probe_device(device, quick=True, cached=True,
|
| 1674 |
+
tenancy_check=False)
|
| 1675 |
+
cmp_rep = compare(ref, cand, gen=gen, inputs=inputs, trials=4,
|
| 1676 |
+
order_shuffle=order_shuffle)
|
| 1677 |
+
fuzz_rep = None
|
| 1678 |
+
if quick_fuzz:
|
| 1679 |
+
fuzz_rep = fuzz_shapes(
|
| 1680 |
+
cand, ref, make=make, arity=arity,
|
| 1681 |
+
shapes=[(1, 17), (3, 63), (128, 128)],
|
| 1682 |
+
variants=("contig", "transposed", "offset_slice"),
|
| 1683 |
+
dtypes=(args0[0].dtype,))
|
| 1684 |
+
bench_rep = None
|
| 1685 |
+
if inputs is not None:
|
| 1686 |
+
bench_rep = bench(lambda: cand(*inputs), iters=32, warmup=6,
|
| 1687 |
+
bytes=bytes, flops=flops, dossier=dossier,
|
| 1688 |
+
dtype=dtype, device=device)
|
| 1689 |
+
passed = cmp_rep["pass"] and (fuzz_rep is None or
|
| 1690 |
+
fuzz_rep["failed"] == 0)
|
| 1691 |
+
parts = [cmp_rep["verdict"]]
|
| 1692 |
+
if fuzz_rep is not None:
|
| 1693 |
+
parts.append(fuzz_rep["verdict"])
|
| 1694 |
+
if bench_rep is not None:
|
| 1695 |
+
parts.append(bench_rep["verdict"])
|
| 1696 |
+
rep = {"pass": passed, "verdict" : " | ".join(parts),
|
| 1697 |
+
"compare": cmp_rep, "fuzz": fuzz_rep, "bench": bench_rep,
|
| 1698 |
+
"device": dossier["device"]["name"]}
|
| 1699 |
+
hints = list(cmp_rep.get("hints", []))
|
| 1700 |
+
if fuzz_rep is not None:
|
| 1701 |
+
hints += fuzz_rep.get("hints", [])
|
| 1702 |
+
if hints:
|
| 1703 |
+
rep["hints"] = hints
|
| 1704 |
+
return rep
|
| 1705 |
+
|
| 1706 |
+
|
| 1707 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1708 |
+
"""Device buffer for MK_STAMP (1-D) or MK_STAMP_BLOCK ([blocks, n]) stamps."""
|
|
|
|
|
|
|
| 1709 |
if blocks is not None:
|
| 1710 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1711 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
|
|
| 1715 |
|
| 1716 |
|
| 1717 |
def pinned_dossiers():
|
| 1718 |
+
"""Names of the release-pinned dossiers, one per certified architecture."""
|
|
|
|
| 1719 |
return sorted(_PINNED.keys())
|
| 1720 |
|
| 1721 |
|
|
|
|
| 1734 |
|
| 1735 |
|
| 1736 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1737 |
+
"""Per-leaf relative drift of a fresh dossier against the release pin for the same architecture."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1738 |
if pinned is None:
|
| 1739 |
arch = current["device"]["sm_arch"]
|
| 1740 |
matches = [k for k, v in _PINNED.items()
|
|
|
|
| 1757 |
"rel": round(rel, 4)})
|
| 1758 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1759 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1760 |
+
rep = {"entries": entries, "worst_rel": worst, "over_tol": over,
|
| 1761 |
+
"rel_tol": rel_tol, "compared": len(entries),
|
| 1762 |
+
"pass": len(over) == 0}
|
| 1763 |
+
rep["verdict"] = ("pass: worst drift %.3f over %d entries" %
|
| 1764 |
+
(worst, len(entries))) if rep["pass"] else \
|
| 1765 |
+
("fail: %d entries over %.2f, worst %s at %.3f" %
|
| 1766 |
+
(len(over), rel_tol, over[0]["path"], worst))
|
| 1767 |
+
return rep
|
| 1768 |
|
| 1769 |
|
| 1770 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1771 |
device="cuda"):
|
| 1772 |
+
"""Run the shipped MK_STAMP_BLOCK example (load, compute, drained store) and decode its stamps."""
|
|
|
|
|
|
|
| 1773 |
props = torch.cuda.get_device_properties(device)
|
| 1774 |
if blocks is None:
|
| 1775 |
blocks = props.multi_processor_count
|
|
|
|
| 1782 |
|
| 1783 |
|
| 1784 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1785 |
+
"""Decode MK_STAMP deltas to ticks and microseconds; 2-D buffers yield min/median/max across blocks."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1786 |
if clock_ghz is None:
|
| 1787 |
clock_ghz = measure_clock(stamps.device)
|
| 1788 |
|
|
|
|
| 1818 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1819 |
"blocks_reporting": int(live.sum().item()),
|
| 1820 |
"blocks_total": int(live.numel())}
|
| 1821 |
+
|
| 1822 |
+
|
| 1823 |
+
_SCHEMAS = {
|
| 1824 |
+
"probe_device": {
|
| 1825 |
+
"device": "name, sm_arch, sms, l2_bytes, vram_bytes, torch, "
|
| 1826 |
+
"cuda_runtime, ecc, driver, power_limit_w",
|
| 1827 |
+
"clock": "max/min/final GHz under sustained FMA load plus sag_pct",
|
| 1828 |
+
"dram_bw": "GB/s: triad_4B/8B/16B, read_only, write_only, "
|
| 1829 |
+
"random_gather_16B",
|
| 1830 |
+
"l2_bw": "GB/s over an L2-resident working set",
|
| 1831 |
+
"latency": "cycles: smem, l1, l2, dram (min of 3 chases) plus "
|
| 1832 |
+
"ns_per_cycle",
|
| 1833 |
+
"fma_f32 / fma_f64": "TFLOP/s of the register-resident pipes",
|
| 1834 |
+
"fma_ilp": "TFLOP/s at 1/2/4 chains plus ratio_4_1; near 4 means "
|
| 1835 |
+
"latency-bound",
|
| 1836 |
+
"mma": "TFLOP/s per dtype; sparse carries executed and "
|
| 1837 |
+
"dense_equivalent; verified_k_lanes carries "
|
| 1838 |
+
"executed_k_per_iter, vs_nominal, calibration_linear; "
|
| 1839 |
+
"fp8_accum carries the accumulate-precision stall",
|
| 1840 |
+
"mma_fed": "TFLOP/s of smem-staged fp16 tiles (staging exposed)",
|
| 1841 |
+
"smem_bw": "GB/s aggregate, conflict-light pattern",
|
| 1842 |
+
"smem_conflict_penalty": "time ratio vs stride 1 per lane stride",
|
| 1843 |
+
"transfer_bw": "GB/s: h2d/d2h pinned and pageable, d2d",
|
| 1844 |
+
"atomics_f32": "Gop/s curve over distinct addresses plus "
|
| 1845 |
+
"shared_same_addr",
|
| 1846 |
+
"launch_overhead": "us: torch_queued, torch_round_trip, "
|
| 1847 |
+
"raw_queued, raw_round_trip",
|
| 1848 |
+
"grid_barrier": "us per grid.sync or 'unsupported'",
|
| 1849 |
+
"occupancy_blocks_per_sm": "API table by (threads, dyn smem)",
|
| 1850 |
+
"occupancy_knees_fma_tflops": "timed dependent-chain table",
|
| 1851 |
+
},
|
| 1852 |
+
"bench": {
|
| 1853 |
+
"median_ms": "median over accepted samples",
|
| 1854 |
+
"sustained_ms": "median over all samples",
|
| 1855 |
+
"median_ci95_ms / p05_ms / p95_ms / min_ms / mean_ms":
|
| 1856 |
+
"distribution of accepted samples",
|
| 1857 |
+
"samples / rejected_throttled / rejection_basis":
|
| 1858 |
+
"counts and whether rejection used sm_clock or "
|
| 1859 |
+
"bandwidth_consistency",
|
| 1860 |
+
"no_clean_window": "present when every window ran throttled; the "
|
| 1861 |
+
"median is then a throttled-regime number",
|
| 1862 |
+
"clock_ghz": "best and worst SM clock probes around the run",
|
| 1863 |
+
"achieved_gbs / achieved_tflops / arithmetic_intensity":
|
| 1864 |
+
"rates from declared traffic",
|
| 1865 |
+
"roofline": "bound, ridge_flops_per_byte, pct_of_roof, peak_dtype",
|
| 1866 |
+
"graph_captured / graph_fallback_reason": "CUDA-graph state",
|
| 1867 |
+
"alloc_growth_bytes_per_call": "present when fn leaks allocations",
|
| 1868 |
+
"host_overhead_suspected": "present when fn syncs or does host "
|
| 1869 |
+
"work inside; ops under ~50 us are "
|
| 1870 |
+
"below the detection floor",
|
| 1871 |
+
"verdict": "one-sentence summary",
|
| 1872 |
+
},
|
| 1873 |
+
"compare": {
|
| 1874 |
+
"pass": "final verdict incl. the one-sided bias gate",
|
| 1875 |
+
"verdict / criterion / hints": "summary, acceptance rule, and "
|
| 1876 |
+
"failure-pattern suggestions",
|
| 1877 |
+
"worst_trial": "max_ulp, mean_ulp, first_divergent_index(+_nd), "
|
| 1878 |
+
"NaN/Inf census, order_band_ulp, band_score, "
|
| 1879 |
+
"frac_above_band, bias_one_sided_frac, "
|
| 1880 |
+
"worst_elements (index, index_nd, ref, cand, ulp)",
|
| 1881 |
+
"max_abs / max_rel / ulp_hist_log2": "aggregate evidence",
|
| 1882 |
+
"floor": "the banded route cannot reject errors under "
|
| 1883 |
+
"band_margin*(band+2) ULP at order-stable elements",
|
| 1884 |
+
},
|
| 1885 |
+
"fuzz_shapes": {
|
| 1886 |
+
"cases": "per-case shape, variant, dtype, status, repro recipe",
|
| 1887 |
+
"status values": "ok | mismatch | exception | context_killed | "
|
| 1888 |
+
"context_lost | crashed_isolated | skipped_vram "
|
| 1889 |
+
"| skipped_empty",
|
| 1890 |
+
"failed / total / context_lost / verdict / hints": "aggregates",
|
| 1891 |
+
},
|
| 1892 |
+
"sweep": {
|
| 1893 |
+
"rows": "grid point plus median_ms, rejected_throttled, check?, "
|
| 1894 |
+
"achieved rates, status in pruned_race | not_run",
|
| 1895 |
+
"verdict": "on the fastest row",
|
| 1896 |
+
},
|
| 1897 |
+
"dossier_drift": {
|
| 1898 |
+
"entries": "path, current, pinned, rel per intersecting leaf",
|
| 1899 |
+
"worst_rel / over_tol / compared / pass / verdict": "aggregates",
|
| 1900 |
+
},
|
| 1901 |
+
"judge": {
|
| 1902 |
+
"pass / verdict / hints": "composite over compare, fuzz, bench",
|
| 1903 |
+
"compare / fuzz / bench": "the full sub-reports",
|
| 1904 |
+
},
|
| 1905 |
+
"read_stamps": {
|
| 1906 |
+
"phases": "ticks and us per phase; 2-D input adds min/median/max "
|
| 1907 |
+
"across blocks",
|
| 1908 |
+
"note": "stamps record issue; DRAIN variants include the store "
|
| 1909 |
+
"drain to the coherence point",
|
| 1910 |
+
},
|
| 1911 |
+
}
|
| 1912 |
+
|
| 1913 |
+
|
| 1914 |
+
def schema(name=None):
|
| 1915 |
+
"""Field layout of every report, so an agent can look up what a number means."""
|
| 1916 |
+
if name is None:
|
| 1917 |
+
return _SCHEMAS
|
| 1918 |
+
if name not in _SCHEMAS:
|
| 1919 |
+
raise KeyError(f"no schema for {name!r}; have "
|
| 1920 |
+
f"{sorted(_SCHEMAS)}")
|
| 1921 |
+
return _SCHEMAS[name]
|
build/{torch212-cxx11-cu130-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so → torch212-cxx11-cu132-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5db8a870d3df57f2fab7ca97d0d323183a1985635ee09af76797864ecd65520e
|
| 3 |
+
size 1438880
|
build/torch212-cxx11-cu132-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _metakernel_cuda_0b9de42
|
| 3 |
+
ops = torch.ops._metakernel_cuda_0b9de42
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_metakernel_cuda_0b9de42::{op_name}"
|
build/torch212-cxx11-cu132-x86_64-linux/_pinned.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
Generated by dev/pin_dossier.py from full probe_device() runs on idle,
|
| 4 |
-
single-tenant cards; consumed by metakernel.dossier_drift.
|
| 5 |
-
"""
|
| 6 |
|
| 7 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 8 |
'confidence': 'high',
|
|
@@ -295,7 +291,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 295 |
'd2h_pinned': 10.8,
|
| 296 |
'h2d_pageable': 10.3,
|
| 297 |
'h2d_pinned': 11.8}}},
|
| 298 |
-
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.
|
| 299 |
'confidence': 'high',
|
| 300 |
'method': 'atomicAdd '
|
| 301 |
'hammer; curve '
|
|
@@ -308,21 +304,21 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 308 |
'contention) to '
|
| 309 |
'one per thread',
|
| 310 |
'units': 'Gop/s',
|
| 311 |
-
'value': {'curve_slots': {'1': 0.
|
| 312 |
-
'145408':
|
| 313 |
-
'4096':
|
| 314 |
-
'512':
|
| 315 |
-
'64': 13.
|
| 316 |
-
'8': 5.
|
| 317 |
-
'shared_same_addr': 12.
|
| 318 |
'clock': {'confidence': 'high',
|
| 319 |
-
'final_ghz': 2.
|
| 320 |
-
'max_ghz': 2.
|
| 321 |
'method': '24 back-to-back FMA '
|
| 322 |
'spin windows of 30M '
|
| 323 |
'ticks; wall/ticks '
|
| 324 |
'per window',
|
| 325 |
-
'min_ghz': 2.
|
| 326 |
'sag_pct': 0.3,
|
| 327 |
'units': 'GHz'},
|
| 328 |
'device': {'cuda_runtime': '12.6',
|
|
@@ -336,7 +332,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 336 |
'sms': 142,
|
| 337 |
'torch': '2.10.0+cu126',
|
| 338 |
'vram_bytes': 48305274880},
|
| 339 |
-
'dram_bw': {'clock_ghz': 2.
|
| 340 |
'confidence': 'high',
|
| 341 |
'method': 'streaming triad '
|
| 342 |
'c=a+s*b per access '
|
|
@@ -356,26 +352,41 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 356 |
'line-efficiency '
|
| 357 |
'claim',
|
| 358 |
'units': 'GB/s',
|
| 359 |
-
'value': {'random_gather_16B': 218.
|
| 360 |
-
'read_only':
|
| 361 |
-
'triad_16B':
|
| 362 |
-
'triad_4B':
|
| 363 |
-
'triad_8B':
|
| 364 |
'write_only': 766.5}},
|
| 365 |
-
'fma_f32': {'clock_ghz': 2.
|
| 366 |
'confidence': 'high',
|
| 367 |
'method': 'register-resident '
|
| 368 |
'FMA chains',
|
| 369 |
'units': 'TFLOP/s',
|
| 370 |
-
'value':
|
| 371 |
-
'fma_f64': {'clock_ghz': 2.
|
| 372 |
'confidence': 'high',
|
| 373 |
'method': 'register-resident '
|
| 374 |
'double FMA chains '
|
| 375 |
'(the DFMA pipe)',
|
| 376 |
'units': 'TFLOP/s',
|
| 377 |
-
'value': 1.
|
| 378 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
'confidence': 'high',
|
| 380 |
'method': 'cooperative '
|
| 381 |
'kernel, 142 '
|
|
@@ -383,8 +394,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 383 |
'threads',
|
| 384 |
'units': 'us per '
|
| 385 |
'grid.sync',
|
| 386 |
-
'value': 0.
|
| 387 |
-
'l2_bw': {'clock_ghz': 2.
|
| 388 |
'confidence': 'medium',
|
| 389 |
'method': 'in-kernel repeated '
|
| 390 |
'triad passes over 72 '
|
|
@@ -394,14 +405,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 394 |
'from HBM; amortized '
|
| 395 |
'over 64 passes',
|
| 396 |
'units': 'GB/s',
|
| 397 |
-
'value':
|
| 398 |
-
'latency': {'clock_ghz': 2.
|
| 399 |
'confidence': 'high',
|
| 400 |
'method': 'Sattolo-cycle '
|
| 401 |
-
'pointer chase
|
| 402 |
-
'
|
| 403 |
-
'
|
| 404 |
-
'ring
|
|
|
|
| 405 |
'self-evicting '
|
| 406 |
'chain (2x L2 in '
|
| 407 |
'touched lines) for '
|
|
@@ -413,40 +425,58 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 413 |
'footprints',
|
| 414 |
'units': 'cycles (single-lane '
|
| 415 |
'dependent loads)',
|
| 416 |
-
'value': {'dram_cycles':
|
| 417 |
'l1_cycles': 45.8,
|
| 418 |
-
'l2_cycles':
|
| 419 |
'ns_per_cycle': 0.372,
|
| 420 |
'smem_cycles': 30.0}},
|
| 421 |
-
'launch_overhead': {'clock_ghz': 2.
|
| 422 |
-
'confidence': '
|
| 423 |
'method': 'empty-kernel '
|
| 424 |
-
'trains
|
| 425 |
-
'
|
| 426 |
-
'launch
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
'units': 'us',
|
| 432 |
-
'value': {'
|
| 433 |
-
'
|
| 434 |
-
|
|
|
|
|
|
|
| 435 |
'confidence': 'medium',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
'method': 'register-fragment '
|
| 437 |
'mma_sync loops (wmma; '
|
| 438 |
'fp8 and 2:4-sparse '
|
| 439 |
'fp16 via inline PTX '
|
| 440 |
-
'm16n8k32;
|
| 441 |
-
'
|
| 442 |
-
'
|
| 443 |
-
'
|
| 444 |
-
'
|
| 445 |
-
'
|
| 446 |
-
'
|
| 447 |
-
'
|
| 448 |
-
'
|
| 449 |
-
'
|
| 450 |
'note': 'dense-tile rate without '
|
| 451 |
'memory traffic; real '
|
| 452 |
'kernels see this only '
|
|
@@ -454,25 +484,54 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 454 |
'behind it',
|
| 455 |
'units': 'TFLOP/s (TOP/s for '
|
| 456 |
'int8)',
|
| 457 |
-
'value': {'bf16':
|
| 458 |
-
'fp16':
|
| 459 |
-
'fp16_sparse_2_4':
|
| 460 |
-
|
| 461 |
-
'
|
| 462 |
-
'
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
| 464 |
'vs_nominal': 1.0},
|
| 465 |
-
'fp16': {'
|
|
|
|
| 466 |
'vs_nominal': 1.0},
|
| 467 |
-
'fp16_sparse_2_4': {'
|
|
|
|
| 468 |
'vs_nominal': 1.0},
|
| 469 |
-
'fp8_e4m3': {'
|
| 470 |
-
'
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
'vs_nominal': 1.0},
|
| 473 |
-
'tf32': {'
|
|
|
|
| 474 |
'vs_nominal': 1.0}}},
|
| 475 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
'confidence': 'high',
|
| 477 |
'method': 'occupancy '
|
| 478 |
'API '
|
|
@@ -501,7 +560,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 501 |
'16KB': 3,
|
| 502 |
'32KB': 3,
|
| 503 |
'48KB': 2}}},
|
| 504 |
-
'occupancy_knees_fma_tflops': {'clock_ghz': 2.
|
| 505 |
'confidence': 'high',
|
| 506 |
'method': 'a '
|
| 507 |
'single '
|
|
@@ -521,17 +580,6 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 521 |
'fixed '
|
| 522 |
'total '
|
| 523 |
'work; '
|
| 524 |
-
'throughput '
|
| 525 |
-
'is '
|
| 526 |
-
'proportional '
|
| 527 |
-
'to '
|
| 528 |
-
'resident '
|
| 529 |
-
'threads '
|
| 530 |
-
'until '
|
| 531 |
-
'the '
|
| 532 |
-
'pipe '
|
| 533 |
-
'saturates, '
|
| 534 |
-
'so '
|
| 535 |
'the '
|
| 536 |
'cliff '
|
| 537 |
'marks '
|
|
@@ -549,15 +597,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 549 |
'above',
|
| 550 |
'units': 'TFLOP/s',
|
| 551 |
'value': {'t1024': {'0KB': 75.5,
|
| 552 |
-
'32KB': 73.
|
| 553 |
-
'48KB':
|
| 554 |
-
't128': {'0KB': 79.
|
| 555 |
'32KB': 60.31,
|
| 556 |
-
'48KB':
|
| 557 |
-
't256': {'0KB':
|
| 558 |
-
'32KB':
|
| 559 |
-
'48KB':
|
| 560 |
-
'smem_bw': {'clock_ghz': 2.
|
| 561 |
'confidence': 'medium',
|
| 562 |
'method': 'block-resident '
|
| 563 |
'read+write '
|
|
@@ -566,12 +614,56 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 566 |
'aggregated across '
|
| 567 |
'SMs',
|
| 568 |
'note': 'bank-conflict-light '
|
| 569 |
-
'access pattern;
|
| 570 |
-
'
|
| 571 |
-
'
|
|
|
|
| 572 |
'units': 'GB/s',
|
| 573 |
-
'value':
|
| 574 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
'confidence': 'high',
|
| 576 |
'method': '256 MB torch '
|
| 577 |
'copies, '
|
|
@@ -583,8 +675,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 583 |
'direction of '
|
| 584 |
'traffic',
|
| 585 |
'units': 'GB/s',
|
| 586 |
-
'value': {'d2d':
|
| 587 |
-
'd2h_pageable':
|
| 588 |
-
'd2h_pinned': 26.
|
| 589 |
-
'h2d_pageable':
|
| 590 |
'h2d_pinned': 24.0}}}}
|
|
|
|
| 1 |
+
'''Release-pinned device dossiers, one per certified architecture; generated by dev/pin_dossier.py from idle single-tenant runs, consumed by dossier_drift.'''
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 4 |
'confidence': 'high',
|
|
|
|
| 291 |
'd2h_pinned': 10.8,
|
| 292 |
'h2d_pageable': 10.3,
|
| 293 |
'h2d_pinned': 11.8}}},
|
| 294 |
+
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.705,
|
| 295 |
'confidence': 'high',
|
| 296 |
'method': 'atomicAdd '
|
| 297 |
'hammer; curve '
|
|
|
|
| 304 |
'contention) to '
|
| 305 |
'one per thread',
|
| 306 |
'units': 'Gop/s',
|
| 307 |
+
'value': {'curve_slots': {'1': 0.71,
|
| 308 |
+
'145408': 413.99,
|
| 309 |
+
'4096': 184.9,
|
| 310 |
+
'512': 49.84,
|
| 311 |
+
'64': 13.25,
|
| 312 |
+
'8': 5.74},
|
| 313 |
+
'shared_same_addr': 12.23}},
|
| 314 |
'clock': {'confidence': 'high',
|
| 315 |
+
'final_ghz': 2.698,
|
| 316 |
+
'max_ghz': 2.705,
|
| 317 |
'method': '24 back-to-back FMA '
|
| 318 |
'spin windows of 30M '
|
| 319 |
'ticks; wall/ticks '
|
| 320 |
'per window',
|
| 321 |
+
'min_ghz': 2.5,
|
| 322 |
'sag_pct': 0.3,
|
| 323 |
'units': 'GHz'},
|
| 324 |
'device': {'cuda_runtime': '12.6',
|
|
|
|
| 332 |
'sms': 142,
|
| 333 |
'torch': '2.10.0+cu126',
|
| 334 |
'vram_bytes': 48305274880},
|
| 335 |
+
'dram_bw': {'clock_ghz': 2.705,
|
| 336 |
'confidence': 'high',
|
| 337 |
'method': 'streaming triad '
|
| 338 |
'c=a+s*b per access '
|
|
|
|
| 352 |
'line-efficiency '
|
| 353 |
'claim',
|
| 354 |
'units': 'GB/s',
|
| 355 |
+
'value': {'random_gather_16B': 218.1,
|
| 356 |
+
'read_only': 766.5,
|
| 357 |
+
'triad_16B': 682.1,
|
| 358 |
+
'triad_4B': 654.8,
|
| 359 |
+
'triad_8B': 670.9,
|
| 360 |
'write_only': 766.5}},
|
| 361 |
+
'fma_f32': {'clock_ghz': 2.705,
|
| 362 |
'confidence': 'high',
|
| 363 |
'method': 'register-resident '
|
| 364 |
'FMA chains',
|
| 365 |
'units': 'TFLOP/s',
|
| 366 |
+
'value': 86.58},
|
| 367 |
+
'fma_f64': {'clock_ghz': 2.705,
|
| 368 |
'confidence': 'high',
|
| 369 |
'method': 'register-resident '
|
| 370 |
'double FMA chains '
|
| 371 |
'(the DFMA pipe)',
|
| 372 |
'units': 'TFLOP/s',
|
| 373 |
+
'value': 1.257},
|
| 374 |
+
'fma_ilp': {'clock_ghz': 2.705,
|
| 375 |
+
'confidence': 'high',
|
| 376 |
+
'method': '1/2/4 independent '
|
| 377 |
+
'dependent FMA '
|
| 378 |
+
'chains per thread '
|
| 379 |
+
'at one block of 64 '
|
| 380 |
+
'threads per SM; a '
|
| 381 |
+
'ratio near 4 means '
|
| 382 |
+
'latency-bound at '
|
| 383 |
+
'that occupancy',
|
| 384 |
+
'units': 'TFLOP/s',
|
| 385 |
+
'value': {'ilp1': 11.75,
|
| 386 |
+
'ilp2': 23.37,
|
| 387 |
+
'ilp4': 43.03,
|
| 388 |
+
'ratio_4_1': 3.66}},
|
| 389 |
+
'grid_barrier': {'clock_ghz': 2.705,
|
| 390 |
'confidence': 'high',
|
| 391 |
'method': 'cooperative '
|
| 392 |
'kernel, 142 '
|
|
|
|
| 394 |
'threads',
|
| 395 |
'units': 'us per '
|
| 396 |
'grid.sync',
|
| 397 |
+
'value': 0.55},
|
| 398 |
+
'l2_bw': {'clock_ghz': 2.705,
|
| 399 |
'confidence': 'medium',
|
| 400 |
'method': 'in-kernel repeated '
|
| 401 |
'triad passes over 72 '
|
|
|
|
| 405 |
'from HBM; amortized '
|
| 406 |
'over 64 passes',
|
| 407 |
'units': 'GB/s',
|
| 408 |
+
'value': 9590.6},
|
| 409 |
+
'latency': {'clock_ghz': 2.691,
|
| 410 |
'confidence': 'high',
|
| 411 |
'method': 'Sattolo-cycle '
|
| 412 |
+
'pointer chase, min '
|
| 413 |
+
'of 3 runs; 16 KB '
|
| 414 |
+
'ring for L1, 2 MB '
|
| 415 |
+
'TLB-resident ring '
|
| 416 |
+
'for L2, '
|
| 417 |
'self-evicting '
|
| 418 |
'chain (2x L2 in '
|
| 419 |
'touched lines) for '
|
|
|
|
| 425 |
'footprints',
|
| 426 |
'units': 'cycles (single-lane '
|
| 427 |
'dependent loads)',
|
| 428 |
+
'value': {'dram_cycles': 656.9,
|
| 429 |
'l1_cycles': 45.8,
|
| 430 |
+
'l2_cycles': 292.0,
|
| 431 |
'ns_per_cycle': 0.372,
|
| 432 |
'smem_cycles': 30.0}},
|
| 433 |
+
'launch_overhead': {'clock_ghz': 2.705,
|
| 434 |
+
'confidence': 'medium',
|
| 435 |
'method': 'empty-kernel '
|
| 436 |
+
'trains; '
|
| 437 |
+
'torch path '
|
| 438 |
+
'per-launch '
|
| 439 |
+
'through op '
|
| 440 |
+
'dispatch, '
|
| 441 |
+
'raw path '
|
| 442 |
+
'looped '
|
| 443 |
+
'inside one '
|
| 444 |
+
'binding '
|
| 445 |
+
'call',
|
| 446 |
+
'note': 'host-state-dependent; '
|
| 447 |
+
'the '
|
| 448 |
+
'torch-minus-raw '
|
| 449 |
+
'gap is the '
|
| 450 |
+
'dispatch '
|
| 451 |
+
'cost a '
|
| 452 |
+
'torch-launched '
|
| 453 |
+
'candidate '
|
| 454 |
+
'pays',
|
| 455 |
'units': 'us',
|
| 456 |
+
'value': {'raw_queued_us': 6.75,
|
| 457 |
+
'raw_round_trip_us': 13.87,
|
| 458 |
+
'torch_queued_us': 7.09,
|
| 459 |
+
'torch_round_trip_us': 18.53}},
|
| 460 |
+
'mma': {'clock_ghz': 2.705,
|
| 461 |
'confidence': 'medium',
|
| 462 |
+
'fp8_accum': {'fp8_e4m3': {'effective_accum_mantissa_bits': 8,
|
| 463 |
+
'stall_element_value': 16384.0},
|
| 464 |
+
'fp8_e5m2': {'effective_accum_mantissa_bits': 8,
|
| 465 |
+
'stall_element_value': 16384.0}},
|
| 466 |
'method': 'register-fragment '
|
| 467 |
'mma_sync loops (wmma; '
|
| 468 |
'fp8 and 2:4-sparse '
|
| 469 |
'fp16 via inline PTX '
|
| 470 |
+
'm16n8k32); flops '
|
| 471 |
+
'derive from a '
|
| 472 |
+
'sub-saturation '
|
| 473 |
+
'accumulator read-back '
|
| 474 |
+
'with every fragment '
|
| 475 |
+
'element observed, '
|
| 476 |
+
'never the declared '
|
| 477 |
+
'loop shape; sparse '
|
| 478 |
+
'reports executed and '
|
| 479 |
+
'dense-equivalent flops',
|
| 480 |
'note': 'dense-tile rate without '
|
| 481 |
'memory traffic; real '
|
| 482 |
'kernels see this only '
|
|
|
|
| 484 |
'behind it',
|
| 485 |
'units': 'TFLOP/s (TOP/s for '
|
| 486 |
'int8)',
|
| 487 |
+
'value': {'bf16': 369.6,
|
| 488 |
+
'fp16': 365.3,
|
| 489 |
+
'fp16_sparse_2_4': {'dense_equivalent': 711.1,
|
| 490 |
+
'executed': 355.6},
|
| 491 |
+
'fp8_e4m3': 772.3,
|
| 492 |
+
'fp8_e5m2': 771.2,
|
| 493 |
+
'int8': 750.0,
|
| 494 |
+
'tf32': 178.3},
|
| 495 |
+
'verified_k_lanes': {'bf16': {'calibration_linear': True,
|
| 496 |
+
'executed_k_per_iter': 64.0,
|
| 497 |
'vs_nominal': 1.0},
|
| 498 |
+
'fp16': {'calibration_linear': True,
|
| 499 |
+
'executed_k_per_iter': 64.0,
|
| 500 |
'vs_nominal': 1.0},
|
| 501 |
+
'fp16_sparse_2_4': {'calibration_linear': True,
|
| 502 |
+
'executed_k_per_iter': 128.0,
|
| 503 |
'vs_nominal': 1.0},
|
| 504 |
+
'fp8_e4m3': {'calibration_linear': True,
|
| 505 |
+
'executed_k_per_iter': 256.0,
|
| 506 |
+
'vs_nominal': 1.0},
|
| 507 |
+
'fp8_e5m2': {'calibration_linear': True,
|
| 508 |
+
'executed_k_per_iter': 256.0,
|
| 509 |
+
'vs_nominal': 1.0},
|
| 510 |
+
'int8': {'calibration_linear': True,
|
| 511 |
+
'executed_k_per_iter': 64.0,
|
| 512 |
'vs_nominal': 1.0},
|
| 513 |
+
'tf32': {'calibration_linear': True,
|
| 514 |
+
'executed_k_per_iter': 32.0,
|
| 515 |
'vs_nominal': 1.0}}},
|
| 516 |
+
'mma_fed': {'clock_ghz': 2.705,
|
| 517 |
+
'confidence': 'high',
|
| 518 |
+
'method': 'm16n16k16 fp16 '
|
| 519 |
+
'tiles fed from an '
|
| 520 |
+
'L2-resident buffer '
|
| 521 |
+
'through cp.async '
|
| 522 |
+
'double-buffered '
|
| 523 |
+
'shared memory; '
|
| 524 |
+
'accumulator-verified',
|
| 525 |
+
'note': 'the achieved '
|
| 526 |
+
'counterpart to the '
|
| 527 |
+
'register peak: '
|
| 528 |
+
'staging exposed, one '
|
| 529 |
+
'dependent '
|
| 530 |
+
'accumulator chain '
|
| 531 |
+
'per warp',
|
| 532 |
+
'units': 'TFLOP/s',
|
| 533 |
+
'value': 85.2},
|
| 534 |
+
'occupancy_blocks_per_sm': {'clock_ghz': 2.705,
|
| 535 |
'confidence': 'high',
|
| 536 |
'method': 'occupancy '
|
| 537 |
'API '
|
|
|
|
| 560 |
'16KB': 3,
|
| 561 |
'32KB': 3,
|
| 562 |
'48KB': 2}}},
|
| 563 |
+
'occupancy_knees_fma_tflops': {'clock_ghz': 2.705,
|
| 564 |
'confidence': 'high',
|
| 565 |
'method': 'a '
|
| 566 |
'single '
|
|
|
|
| 580 |
'fixed '
|
| 581 |
'total '
|
| 582 |
'work; '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
'the '
|
| 584 |
'cliff '
|
| 585 |
'marks '
|
|
|
|
| 597 |
'above',
|
| 598 |
'units': 'TFLOP/s',
|
| 599 |
'value': {'t1024': {'0KB': 75.5,
|
| 600 |
+
'32KB': 73.71,
|
| 601 |
+
'48KB': 72.69},
|
| 602 |
+
't128': {'0KB': 79.72,
|
| 603 |
'32KB': 60.31,
|
| 604 |
+
'48KB': 45.81},
|
| 605 |
+
't256': {'0KB': 78.79,
|
| 606 |
+
'32KB': 78.85,
|
| 607 |
+
'48KB': 72.97}}},
|
| 608 |
+
'smem_bw': {'clock_ghz': 2.705,
|
| 609 |
'confidence': 'medium',
|
| 610 |
'method': 'block-resident '
|
| 611 |
'read+write '
|
|
|
|
| 614 |
'aggregated across '
|
| 615 |
'SMs',
|
| 616 |
'note': 'bank-conflict-light '
|
| 617 |
+
'access pattern; the '
|
| 618 |
+
'stride sweep below '
|
| 619 |
+
'prices the '
|
| 620 |
+
'conflicted case',
|
| 621 |
'units': 'GB/s',
|
| 622 |
+
'value': 22244.0},
|
| 623 |
+
'smem_conflict_penalty': {'clock_ghz': 2.705,
|
| 624 |
+
'confidence': 'high',
|
| 625 |
+
'method': 'dependent '
|
| 626 |
+
'read-FMA-write '
|
| 627 |
+
'per '
|
| 628 |
+
'thread '
|
| 629 |
+
'at '
|
| 630 |
+
'warp-lane '
|
| 631 |
+
'bank '
|
| 632 |
+
'distance '
|
| 633 |
+
'`stride`; '
|
| 634 |
+
'stride '
|
| 635 |
+
'32 '
|
| 636 |
+
'puts '
|
| 637 |
+
'every '
|
| 638 |
+
'lane '
|
| 639 |
+
'in '
|
| 640 |
+
'one '
|
| 641 |
+
'bank',
|
| 642 |
+
'note': 'the '
|
| 643 |
+
'direct '
|
| 644 |
+
'conflict '
|
| 645 |
+
'measurement '
|
| 646 |
+
'that '
|
| 647 |
+
'disambiguates '
|
| 648 |
+
'a '
|
| 649 |
+
'bank-conflict '
|
| 650 |
+
'signature '
|
| 651 |
+
'from '
|
| 652 |
+
'an ILP '
|
| 653 |
+
'stall',
|
| 654 |
+
'units': 'x '
|
| 655 |
+
'(time '
|
| 656 |
+
'ratio '
|
| 657 |
+
'vs '
|
| 658 |
+
'stride '
|
| 659 |
+
'1)',
|
| 660 |
+
'value': {'stride_1': 1.0,
|
| 661 |
+
'stride_16': 7.13,
|
| 662 |
+
'stride_2': 1.07,
|
| 663 |
+
'stride_32': 14.08,
|
| 664 |
+
'stride_4': 2.04,
|
| 665 |
+
'stride_8': 3.91}},
|
| 666 |
+
'transfer_bw': {'clock_ghz': 2.705,
|
| 667 |
'confidence': 'high',
|
| 668 |
'method': '256 MB torch '
|
| 669 |
'copies, '
|
|
|
|
| 675 |
'direction of '
|
| 676 |
'traffic',
|
| 677 |
'units': 'GB/s',
|
| 678 |
+
'value': {'d2d': 324.0,
|
| 679 |
+
'd2h_pageable': 7.8,
|
| 680 |
+
'd2h_pinned': 26.3,
|
| 681 |
+
'h2d_pageable': 7.6,
|
| 682 |
'h2d_pinned': 24.0}}}}
|
build/torch212-cxx11-cu132-x86_64-linux/metadata.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 7 |
"backend": {
|
| 8 |
"type": "cuda",
|
| 9 |
"archs": [
|
|
@@ -18,21 +19,20 @@
|
|
| 18 |
"digest": {
|
| 19 |
"algorithm": "sha256",
|
| 20 |
"files": {
|
| 21 |
-
"__init__.py": "
|
| 22 |
-
"
|
| 23 |
-
"_ops.py": "
|
| 24 |
-
"_pinned.py": "
|
| 25 |
-
"metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"provenance": {
|
| 29 |
"kernel-builder": {
|
| 30 |
"version": "0.17.0-dev0",
|
| 31 |
-
"sha": "
|
| 32 |
"dirty": false
|
| 33 |
},
|
| 34 |
"kernel": {
|
| 35 |
-
"sha": "
|
| 36 |
"dirty": false
|
| 37 |
}
|
| 38 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
+
"id": "_metakernel_cuda_0b9de42",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
| 7 |
+
"kernel-depends": [],
|
| 8 |
"backend": {
|
| 9 |
"type": "cuda",
|
| 10 |
"archs": [
|
|
|
|
| 19 |
"digest": {
|
| 20 |
"algorithm": "sha256",
|
| 21 |
"files": {
|
| 22 |
+
"__init__.py": "lDQ+fJ0JnI3sQGR3UMnDovBbG4ciYBsaFjmgYdKDTKI=",
|
| 23 |
+
"_metakernel_cuda_0b9de42.abi3.so": "XbiocNPfV/L6t8qX0NMjGDoZhWNe4Jr3Z5eGTs1lUg4=",
|
| 24 |
+
"_ops.py": "x88Bkm1nxRli0nIu2u4ztaJopS1IAZWdN9LZPfspJdo=",
|
| 25 |
+
"_pinned.py": "UyzMniAjfVMIrIbbrSq92KKo5dX5l/GwV3pMVA+eNtM="
|
|
|
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"provenance": {
|
| 29 |
"kernel-builder": {
|
| 30 |
"version": "0.17.0-dev0",
|
| 31 |
+
"sha": "2c40e10d3da1392245ee81bd05b55fb3103b2b34",
|
| 32 |
"dirty": false
|
| 33 |
},
|
| 34 |
"kernel": {
|
| 35 |
+
"sha": "0b9de42fee8a1e52593f2f1f58460c0acc93d7e8",
|
| 36 |
"dirty": false
|
| 37 |
}
|
| 38 |
}
|
build/torch212-cxx11-cu132-x86_64-linux/metakernel/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 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
CHANGED
|
@@ -1,20 +1,4 @@
|
|
| 1 |
-
"""Measuring instrument for the kernel-design loop: timing, adversarial
|
| 2 |
-
correctness comparison, and measured device facts, as loadable ops.
|
| 3 |
-
|
| 4 |
-
from kernels import get_kernel
|
| 5 |
-
mk = get_kernel("phanerozoic/metakernel", version=1,
|
| 6 |
-
trust_remote_code=True)
|
| 7 |
-
|
| 8 |
-
dossier = mk.probe_device() # measured facts
|
| 9 |
-
report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
|
| 10 |
-
verdict = mk.compare(ref, cand, gen) # ULP evidence
|
| 11 |
-
table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
|
| 12 |
-
|
| 13 |
-
Every op returns plain dicts and tensors. Every dossier entry is
|
| 14 |
-
measured by a dedicated probe kernel, carries its SM clock, and labels
|
| 15 |
-
throttled samples; no hardware counters, so entries carry a confidence
|
| 16 |
-
field.
|
| 17 |
-
"""
|
| 18 |
import math
|
| 19 |
import os
|
| 20 |
import time
|
|
@@ -24,9 +8,7 @@ import torch
|
|
| 24 |
try:
|
| 25 |
from ._ops import ops
|
| 26 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 27 |
-
#
|
| 28 |
-
# checkout there is no generated _ops module, so rebuild the op table
|
| 29 |
-
# from the (cached) JIT extension that load_local advertised.
|
| 30 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 31 |
if not _root:
|
| 32 |
raise
|
|
@@ -48,24 +30,25 @@ try:
|
|
| 48 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 49 |
_PINNED = {}
|
| 50 |
|
| 51 |
-
__all__ = ["probe_device", "bench", "compare", "
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"pinned_dossiers", "
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
#
|
| 62 |
_MMA_KINDS = {
|
| 63 |
-
"fp16": (0, 16 * 16, 16,
|
| 64 |
-
"bf16": (1, 16 * 16, 16,
|
| 65 |
-
"tf32": (2, 16 * 16, 8,
|
| 66 |
-
"int8": (3, 16 * 16, 16,
|
| 67 |
-
"fp8_e4m3": (4, 16 * 8, 32,
|
| 68 |
-
"fp16_sparse_2_4": (5, 16 * 8, 32,
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
|
|
@@ -74,7 +57,7 @@ def _sync():
|
|
| 74 |
|
| 75 |
|
| 76 |
def _events(fn, reps):
|
| 77 |
-
"""Time reps calls of fn with one
|
| 78 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 79 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 80 |
for s, e in zip(starts, stops):
|
|
@@ -86,8 +69,7 @@ def _events(fn, reps):
|
|
| 86 |
|
| 87 |
|
| 88 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 89 |
-
"""Effective SM clock in GHz
|
| 90 |
-
Wall time over elapsed ticks is the frequency the ticks ran at."""
|
| 91 |
props = torch.cuda.get_device_properties(device)
|
| 92 |
if blocks is None:
|
| 93 |
blocks = props.multi_processor_count
|
|
@@ -120,8 +102,7 @@ def _entry(value, units, method, clock_ghz, confidence, note=""):
|
|
| 120 |
|
| 121 |
|
| 122 |
def _sattolo_ring(n, device, generator=None):
|
| 123 |
-
"""
|
| 124 |
-
a pointer chase visits every slot before repeating."""
|
| 125 |
perm = torch.randperm(n, generator=generator)
|
| 126 |
ring = torch.empty(n, dtype=torch.int32)
|
| 127 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
@@ -129,8 +110,7 @@ def _sattolo_ring(n, device, generator=None):
|
|
| 129 |
|
| 130 |
|
| 131 |
def _smi_settings(index):
|
| 132 |
-
"""ECC mode, driver,
|
| 133 |
-
when the tool is unavailable."""
|
| 134 |
import subprocess
|
| 135 |
try:
|
| 136 |
out = subprocess.run(
|
|
@@ -150,10 +130,19 @@ def _smi_settings(index):
|
|
| 150 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 151 |
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def mma_tile_check(device="cuda", trials=8):
|
| 154 |
-
"""Random small-integer m16n16k16 tiles through the wmma
|
| 155 |
-
path, compared exactly to the reference GEMM; pins M, N, K, and
|
| 156 |
-
operand mapping (integer operands make fp32 accumulation exact)."""
|
| 157 |
worst = 0.0
|
| 158 |
for t in range(trials):
|
| 159 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
@@ -171,10 +160,83 @@ def mma_tile_check(device="cuda", trials=8):
|
|
| 171 |
"trials": trials}
|
| 172 |
|
| 173 |
|
| 174 |
-
def
|
| 175 |
-
"""
|
| 176 |
-
|
| 177 |
-
a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
props = torch.cuda.get_device_properties(device)
|
| 179 |
sm = props.multi_processor_count
|
| 180 |
l2 = props.L2_cache_size
|
|
@@ -187,8 +249,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 187 |
"vram_bytes": props.total_memory,
|
| 188 |
"torch": torch.__version__,
|
| 189 |
"cuda_runtime": torch.version.cuda,
|
| 190 |
-
# instrument settings
|
| 191 |
-
# dossier records them (None when nvidia-smi is unavailable)
|
| 192 |
**_smi_settings(torch.device(device).index or 0),
|
| 193 |
}
|
| 194 |
}
|
|
@@ -213,9 +274,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 213 |
}
|
| 214 |
clk = max(traj)
|
| 215 |
|
| 216 |
-
# ---- device-memory bandwidth
|
| 217 |
-
|
| 218 |
-
n4 = 16 << 20 # 16M float4 = 256 MB per buffer
|
| 219 |
if props.total_memory < 4 << 30:
|
| 220 |
n4 = 4 << 20
|
| 221 |
a = torch.rand(n4 * 4, device=device)
|
|
@@ -239,7 +299,6 @@ def probe_device(device="cuda", quick=False):
|
|
| 239 |
dtype=torch.int32).to(device)
|
| 240 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 241 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
| 242 |
-
# traffic accounting: idx reads + 16B random loads + streamed stores
|
| 243 |
hbm["random_gather_16B"] = round(
|
| 244 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 245 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
@@ -250,9 +309,9 @@ def probe_device(device="cuda", quick=False):
|
|
| 250 |
"so its nominal number is the achievable rate for "
|
| 251 |
"random access, not a line-efficiency claim")
|
| 252 |
|
| 253 |
-
# ---- L2 bandwidth
|
| 254 |
ws = max(l2 // 4, 1 << 20)
|
| 255 |
-
nl2 = ws // 16
|
| 256 |
al = a[:nl2 * 4].contiguous()
|
| 257 |
bl = b[:nl2 * 4].contiguous()
|
| 258 |
cl = c[:nl2 * 4].contiguous()
|
|
@@ -267,62 +326,61 @@ def probe_device(device="cuda", quick=False):
|
|
| 267 |
"medium", "first pass streams from HBM; amortized "
|
| 268 |
f"over {passes} passes")
|
| 269 |
|
| 270 |
-
# ---- latencies:
|
| 271 |
-
# L1, fits L2 everywhere, and stays TLB-resident after a warm walk;
|
| 272 |
-
# the DRAM chain touches 2x L2 in lines, so it evicts itself.
|
| 273 |
lat = {}
|
| 274 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
| 275 |
-
ring_s = _sattolo_ring(8192, device)
|
| 276 |
-
ops.mk_chase_shared(ring_s, 1 << 14, out64)
|
| 277 |
-
_sync()
|
| 278 |
-
hops = 1 << 16
|
| 279 |
-
ops.mk_chase_shared(ring_s, hops, out64)
|
| 280 |
-
_sync()
|
| 281 |
-
lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
_sync()
|
| 299 |
-
lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 302 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 303 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
_sync()
|
| 308 |
-
lat["dram_cycles"] = round(int(out64[0].item()) / hops_h, 1)
|
| 309 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 310 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 311 |
-
"Sattolo-cycle pointer chase
|
| 312 |
-
"2 MB TLB-resident ring for L2,
|
| 313 |
-
"chain (2x L2 in touched lines)
|
| 314 |
-
idle_clk, "high",
|
| 315 |
"smem includes index arithmetic; HBM includes "
|
| 316 |
"residual TLB effects at large footprints")
|
| 317 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 318 |
|
| 319 |
-
# ---- fp32 FMA and tensor-core throughput
|
| 320 |
blocks = sm * 4
|
| 321 |
iters = 20_000 if quick else 60_000
|
| 322 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 323 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 324 |
reps))
|
| 325 |
-
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 326 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 327 |
"register-resident FMA chains", clk, "high")
|
| 328 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
@@ -334,43 +392,88 @@ def probe_device(device="cuda", quick=False):
|
|
| 334 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 335 |
"register-resident double FMA chains (the DFMA "
|
| 336 |
"pipe)", clk, "high")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
mma = {}
|
| 338 |
mma_klanes = {}
|
| 339 |
-
|
|
|
|
| 340 |
it = 30_000 if quick else 100_000
|
| 341 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 342 |
if r != 0:
|
| 343 |
mma[name] = "unsupported"
|
| 344 |
continue
|
| 345 |
_sync()
|
| 346 |
-
|
| 347 |
-
# the executed K-lane count (exact at these magnitudes)
|
| 348 |
-
cal_iters = 512
|
| 349 |
-
sink[1] = 0.0
|
| 350 |
-
ops.mk_mma(kind, 1, 1, cal_iters, sink)
|
| 351 |
-
_sync()
|
| 352 |
-
gain_per_iter = float(sink[1].item()) / cal_iters
|
| 353 |
mma_klanes[name] = {
|
| 354 |
-
"executed_k_per_iter": round(
|
| 355 |
-
"vs_nominal": round(
|
|
|
|
| 356 |
}
|
|
|
|
|
|
|
| 357 |
ms = _median(_events(
|
| 358 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 362 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 363 |
-
"2:4-sparse fp16 via inline PTX m16n8k32;
|
| 364 |
-
"
|
| 365 |
-
"flops derive from the mean accumulator read-back "
|
| 366 |
"with every fragment element observed, never the "
|
| 367 |
-
"declared loop shape"
|
|
|
|
| 368 |
clk, "medium",
|
| 369 |
"dense-tile rate without memory traffic; real "
|
| 370 |
"kernels see this only when staging hides behind it")
|
| 371 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
# ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
it_sm = 30_000 if quick else 100_000
|
| 375 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 376 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
@@ -380,8 +483,23 @@ def probe_device(device="cuda", quick=False):
|
|
| 380 |
"block-resident read+write streaming (8 B per "
|
| 381 |
"thread-iteration), aggregated across SMs", clk,
|
| 382 |
"medium",
|
| 383 |
-
"bank-conflict-light access pattern;
|
| 384 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
# ---- PCIe / host transfer bandwidth
|
| 387 |
nbytes = 256 << 20
|
|
@@ -412,9 +530,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 412 |
"d2d counts one direction of traffic")
|
| 413 |
del host_pin, host_pag, dev_t, dev_u
|
| 414 |
|
| 415 |
-
# ---- atomic contention curve
|
| 416 |
-
# addresses the whole grid hammers (slots = 1 is full contention;
|
| 417 |
-
# slots = threads is contention-free), plus the shared-memory point
|
| 418 |
n_thr = blocks * 256
|
| 419 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 420 |
at = {"curve_slots": {}}
|
|
@@ -424,7 +540,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 424 |
buf.zero_()
|
| 425 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 426 |
ms = _median(_events(
|
| 427 |
-
lambda
|
| 428 |
reps))
|
| 429 |
at["curve_slots"][str(slots)] = round(
|
| 430 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
@@ -437,7 +553,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 437 |
"of distinct global addresses from 1 (full "
|
| 438 |
"contention) to one per thread", clk, "high")
|
| 439 |
|
| 440 |
-
# ---- launch overhead
|
| 441 |
anchor = sink
|
| 442 |
n_launch = 500 if quick else 2000
|
| 443 |
_sync()
|
|
@@ -452,10 +568,19 @@ def probe_device(device="cuda", quick=False):
|
|
| 452 |
ops.mk_empty(anchor)
|
| 453 |
_sync()
|
| 454 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
|
|
|
|
|
|
| 455 |
d["launch_overhead"] = _entry(
|
| 456 |
-
{"
|
| 457 |
-
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
# ---- grid barrier
|
| 461 |
bar_iters = 500 if quick else 2000
|
|
@@ -471,9 +596,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 471 |
"unavailable or grid too large", clk,
|
| 472 |
"high")
|
| 473 |
|
| 474 |
-
# ---- occupancy
|
| 475 |
-
# at every (threads, dyn_smem) point; when the forced blocks/SM stop
|
| 476 |
-
# covering memory latency, achieved bandwidth falls off the cliff
|
| 477 |
occ = {}
|
| 478 |
for threads in (128, 256, 512, 1024):
|
| 479 |
row = {}
|
|
@@ -494,8 +617,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 494 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 495 |
knee_iters, sink)
|
| 496 |
ms = _median(_events(
|
| 497 |
-
lambda t=threads,
|
| 498 |
-
knee_blocks, t,
|
| 499 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 500 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 501 |
knees[f"t{threads}"] = row
|
|
@@ -504,27 +627,160 @@ def probe_device(device="cuda", quick=False):
|
|
| 504 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 505 |
"the cliff marks where occupancy stops covering latency",
|
| 506 |
clk, "high", "timing-derived counterpart to the API table above")
|
|
|
|
|
|
|
|
|
|
| 507 |
return d
|
| 508 |
|
| 509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 511 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 512 |
-
device="cuda"):
|
| 513 |
-
"""Median-of-N cudaEvent timing with throttle rejection.
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
around every sample, ~0.3 ms each."""
|
| 523 |
for _ in range(warmup):
|
| 524 |
fn()
|
| 525 |
_sync()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
graph_state = "off"
|
| 527 |
graph_reason = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
if graph:
|
| 529 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 530 |
try:
|
|
@@ -539,10 +795,7 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 539 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 540 |
graph_state = "fallback_eager"
|
| 541 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 542 |
-
# drain the invalidated stream
|
| 543 |
-
# On some torch versions a failed capture also wedges the
|
| 544 |
-
# device RNG with no in-process recovery; timing uses no RNG,
|
| 545 |
-
# but the caller's later randn may raise.
|
| 546 |
rng_ok = True
|
| 547 |
for _attempt in range(5):
|
| 548 |
try:
|
|
@@ -572,25 +825,51 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 572 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 573 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 574 |
best_clk = max(clocks)
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
flat = []
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
kept.append(ms)
|
| 585 |
else:
|
| 586 |
rejected += 1
|
| 587 |
-
|
|
|
|
| 588 |
kept = flat
|
| 589 |
med = _median(kept)
|
| 590 |
ks = sorted(kept)
|
| 591 |
n_k = len(ks)
|
| 592 |
-
# order-statistic 95% CI on the median
|
| 593 |
-
# binomial ranks); degenerate at tiny n, where it spans the sample
|
| 594 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 595 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 596 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
@@ -605,12 +884,25 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 605 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 606 |
"samples": len(flat),
|
| 607 |
"rejected_throttled": rejected,
|
|
|
|
| 608 |
"graph_captured": graph_state == "captured",
|
| 609 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 610 |
"worst": round(min(clocks), 3)},
|
| 611 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
if graph_reason:
|
| 613 |
rep["graph_fallback_reason"] = graph_reason
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
if bytes is not None:
|
| 615 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 616 |
if flops is not None:
|
|
@@ -620,6 +912,8 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 620 |
if dossier is not None and bytes and flops:
|
| 621 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 622 |
mma_v = dossier["mma"]["value"].get(dtype)
|
|
|
|
|
|
|
| 623 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 624 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 625 |
ai = flops / bytes
|
|
@@ -630,13 +924,12 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 630 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 631 |
"peak_dtype": dtype,
|
| 632 |
}
|
|
|
|
| 633 |
return rep
|
| 634 |
|
| 635 |
|
| 636 |
def _to_ordered_int(t):
|
| 637 |
-
"""
|
| 638 |
-
distance: positive floats keep their bits, negative floats mirror
|
| 639 |
-
below zero, so the mapping is monotone in the float value."""
|
| 640 |
if t.dtype == torch.float32:
|
| 641 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 642 |
half = 1 << 31
|
|
@@ -652,11 +945,12 @@ def _to_ordered_int(t):
|
|
| 652 |
|
| 653 |
|
| 654 |
def ulp_diff(a, b):
|
| 655 |
-
"""Elementwise ULP distance
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
|
|
|
| 660 |
if not a.is_floating_point():
|
| 661 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 662 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
@@ -673,9 +967,7 @@ class order_shuffles:
|
|
| 673 |
|
| 674 |
@staticmethod
|
| 675 |
def last_dim():
|
| 676 |
-
"""
|
| 677 |
-
accumulation order of a last-dim reduction changes, the value
|
| 678 |
-
does not."""
|
| 679 |
def shuffle(args, seed):
|
| 680 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 681 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
@@ -685,8 +977,7 @@ class order_shuffles:
|
|
| 685 |
|
| 686 |
@staticmethod
|
| 687 |
def matmul_k():
|
| 688 |
-
"""
|
| 689 |
-
product is unchanged, the accumulation order is not."""
|
| 690 |
def shuffle(args, seed):
|
| 691 |
a_m, b_m = args[0], args[1]
|
| 692 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
@@ -700,37 +991,70 @@ def _as_outputs(x):
|
|
| 700 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 701 |
|
| 702 |
|
| 703 |
-
def
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
worst = {"max_ulp": -1}
|
| 722 |
total_hist = {}
|
| 723 |
per_trial = []
|
| 724 |
max_abs_seen = 0.0
|
| 725 |
max_rel_seen = 0.0
|
|
|
|
| 726 |
for t in range(trials):
|
| 727 |
args = gen(seed + t)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
r_outs = _as_outputs(ref(*args))
|
| 729 |
c_outs = _as_outputs(cand(*args))
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
bands = None
|
| 735 |
band = 0
|
| 736 |
band_score = 0.0
|
|
@@ -754,8 +1078,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 754 |
max_ulp = 0
|
| 755 |
mean_ulp = 0.0
|
| 756 |
first_div = -1
|
|
|
|
| 757 |
n_el = 0
|
| 758 |
n_above_band = 0
|
|
|
|
|
|
|
| 759 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 760 |
worst_elements = []
|
| 761 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
@@ -773,8 +1100,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 773 |
mean_ulp += float(d.double().sum().item())
|
| 774 |
n_el += d.numel()
|
| 775 |
nz = (d > 0)
|
|
|
|
|
|
|
| 776 |
if first_div < 0 and nz.any():
|
| 777 |
first_div = int(nz.flatten().nonzero()[0].item())
|
|
|
|
| 778 |
if nz.any():
|
| 779 |
buckets = torch.clamp(
|
| 780 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
@@ -791,6 +1121,7 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 791 |
if val > 0:
|
| 792 |
worst_elements.append({
|
| 793 |
"output": oi, "index": int(ix),
|
|
|
|
| 794 |
"ref": float(rf[ix].item()),
|
| 795 |
"cand": float(cf[ix].item()),
|
| 796 |
"ulp": int(val)})
|
|
@@ -810,23 +1141,27 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 810 |
inf_r += int(torch.isinf(r).sum().item())
|
| 811 |
inf_c += int(torch.isinf(c).sum().item())
|
| 812 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
|
|
|
| 813 |
trial_rep = {
|
| 814 |
"max_ulp": max_ulp,
|
| 815 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 816 |
"first_divergent_index": first_div,
|
|
|
|
| 817 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 818 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 819 |
"order_band_ulp": band,
|
| 820 |
"band_score": round(band_score, 2),
|
| 821 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
|
|
|
|
|
|
| 822 |
"worst_elements": worst_elements[:worst_k],
|
| 823 |
}
|
| 824 |
per_trial.append(trial_rep)
|
| 825 |
if max_ulp > worst["max_ulp"]:
|
| 826 |
worst = trial_rep
|
| 827 |
-
band_max = max(t["order_band_ulp"] for t in per_trial)
|
| 828 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 829 |
-
|
|
|
|
| 830 |
score = max(t["band_score"] for t in per_trial)
|
| 831 |
ulp_ok = score <= band_margin
|
| 832 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
@@ -841,8 +1176,34 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 841 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 842 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 843 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 844 |
-
|
| 845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
"criterion": criterion,
|
| 847 |
"worst_trial": worst,
|
| 848 |
"max_abs": max_abs_seen,
|
|
@@ -850,6 +1211,9 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 850 |
"trials": per_trial,
|
| 851 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 852 |
}
|
|
|
|
|
|
|
|
|
|
| 853 |
|
| 854 |
|
| 855 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
@@ -865,6 +1229,30 @@ def _context_alive(device):
|
|
| 865 |
return False
|
| 866 |
|
| 867 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
def _fuzz_build(make, shape, dtype):
|
| 869 |
try:
|
| 870 |
out = make(tuple(shape), dtype)
|
|
@@ -881,7 +1269,7 @@ def _fuzz_variant(x, v):
|
|
| 881 |
if v == "strided_rows":
|
| 882 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 883 |
if v == "offset_slice":
|
| 884 |
-
#
|
| 885 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 886 |
if v == "overflow_expand":
|
| 887 |
if x.numel() == 0:
|
|
@@ -895,8 +1283,7 @@ def _fuzz_variant(x, v):
|
|
| 895 |
|
| 896 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 897 |
stop_on_context_loss=True):
|
| 898 |
-
"""All variant cases for one (shape, dtype) group; returns (rows,
|
| 899 |
-
context_lost)."""
|
| 900 |
rows = []
|
| 901 |
bases = _fuzz_build(make, shape, dtype)
|
| 902 |
lost = False
|
|
@@ -933,8 +1320,7 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 933 |
got = op(*xs)
|
| 934 |
want = ref(*xs)
|
| 935 |
if v == "overflow_expand":
|
| 936 |
-
# bounded comparison:
|
| 937 |
-
# which is where int32 flat indexing wraps
|
| 938 |
w = 1 << 20
|
| 939 |
gf, wf = got.flatten(), want.flatten()
|
| 940 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
@@ -959,20 +1345,30 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 959 |
return rows, lost
|
| 960 |
|
| 961 |
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
import
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 974 |
meta = json.load(open(sys.argv[1], "r"))
|
| 975 |
-
sys.path[:0] = meta["sys_path"]
|
| 976 |
try:
|
| 977 |
import kernels
|
| 978 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
@@ -985,85 +1381,165 @@ except Exception:
|
|
| 985 |
import load_local
|
| 986 |
_mk = load_local.load()
|
| 987 |
import torch
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 994 |
"""
|
| 995 |
|
| 996 |
|
| 997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 999 |
-
isolate_timeout_s=180, device="cuda"):
|
| 1000 |
-
"""
|
| 1001 |
-
report ok / mismatch / exception per case.
|
| 1002 |
-
|
| 1003 |
-
`make(shape, dtype)` (or `make(shape)`) builds a tensor or tuple;
|
| 1004 |
-
layout variants derive non-contiguous and misaligned views whose
|
| 1005 |
-
content equals a contiguous copy. `overflow_expand` stride-0-expands
|
| 1006 |
-
past 2**31 logical elements to catch int32 indexing (`skipped_vram`
|
| 1007 |
-
when it would not fit). Every case carries a `repro` recipe.
|
| 1008 |
-
In-process, the CUDA context is health-checked after exceptions and
|
| 1009 |
-
remaining cases marked `context_lost` if killed; `isolate=True`
|
| 1010 |
-
spawns each (shape, dtype) group in a subprocess, so a
|
| 1011 |
-
context-killing candidate costs one group (`crashed_isolated`);
|
| 1012 |
-
op/ref/make must be picklable."""
|
| 1013 |
if shapes is None:
|
| 1014 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1015 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
|
|
|
|
|
|
| 1016 |
|
| 1017 |
rows = []
|
| 1018 |
lost = False
|
| 1019 |
if isolate:
|
| 1020 |
import json
|
| 1021 |
import pickle
|
| 1022 |
-
import subprocess
|
| 1023 |
import sys as _sys
|
| 1024 |
import tempfile
|
| 1025 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1026 |
fns = os.path.join(tmpdir, "fns.pkl")
|
|
|
|
|
|
|
| 1027 |
with open(fns, "wb") as f:
|
| 1028 |
-
pickle.dump(
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
"shape": list(shape),
|
| 1041 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1042 |
"variants": list(variants),
|
| 1043 |
"ulp_tol": ulp_tol, "device": device,
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
try:
|
| 1048 |
-
r = subprocess.run(
|
| 1049 |
-
[_sys.executable, "-c", _ISOLATE_BOOTSTRAP, meta,
|
| 1050 |
-
fns, out], timeout=isolate_timeout_s,
|
| 1051 |
-
capture_output=True)
|
| 1052 |
-
if r.returncode == 0 and os.path.exists(out):
|
| 1053 |
with open(out, "rb") as f:
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
"status": "crashed_isolated",
|
| 1064 |
-
"exitcode": exitcode,
|
| 1065 |
-
"stderr_tail": stderr_tail}]
|
| 1066 |
-
rows.extend(group)
|
| 1067 |
else:
|
| 1068 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1069 |
for gi, (dtype, shape) in enumerate(pending):
|
|
@@ -1080,21 +1556,27 @@ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
|
|
| 1080 |
n_bad = sum(1 for r in rows
|
| 1081 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1082 |
"skipped_empty"))
|
| 1083 |
-
|
| 1084 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
|
| 1086 |
|
| 1087 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1088 |
check=None, budget_s=None, race=False):
|
| 1089 |
-
"""Launch-configuration search
|
| 1090 |
-
bench row per grid point, sorted fastest first. A point whose factory
|
| 1091 |
-
or run raises is reported, not skipped silently. `check(fn) -> bool`
|
| 1092 |
-
is an optional correctness gate recorded per row (a fast wrong config
|
| 1093 |
-
must not win a sweep). `budget_s` caps total sweep time; points not
|
| 1094 |
-
reached are reported as `not_run` rather than silently dropped.
|
| 1095 |
-
`race=True` runs successive halving: a quick pass over every point at
|
| 1096 |
-
iters/4, then the full budget only on the fastest quartile; pruned
|
| 1097 |
-
points keep their quick measurement and status `pruned_race`."""
|
| 1098 |
keys = list(grid.keys())
|
| 1099 |
|
| 1100 |
def expand(i, acc):
|
|
@@ -1159,13 +1641,71 @@ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
|
| 1159 |
continue
|
| 1160 |
rows.append(run_point(p, iters))
|
| 1161 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
return rows
|
| 1163 |
|
| 1164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1165 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1166 |
-
"""Device buffer for
|
| 1167 |
-
stamps.cuh): the candidate kernel writes clock64() into slot i. With
|
| 1168 |
-
`blocks`, allocates the [blocks, n] layout for MK_STAMP_BLOCK."""
|
| 1169 |
if blocks is not None:
|
| 1170 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1171 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
@@ -1175,8 +1715,7 @@ _DRIFT_SKIP = ("device", "clock", "launch_overhead", "transfer_bw")
|
|
| 1175 |
|
| 1176 |
|
| 1177 |
def pinned_dossiers():
|
| 1178 |
-
"""Names of the
|
| 1179 |
-
architecture)."""
|
| 1180 |
return sorted(_PINNED.keys())
|
| 1181 |
|
| 1182 |
|
|
@@ -1195,11 +1734,7 @@ def _numeric_leaves(d, prefix=""):
|
|
| 1195 |
|
| 1196 |
|
| 1197 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1198 |
-
"""
|
| 1199 |
-
same architecture: per-leaf relative drift, worst drift, entries
|
| 1200 |
-
over `rel_tol`. Host-dependent entries and raw clocks are skipped by
|
| 1201 |
-
default (`skip=()` compares everything); `pinned=None` auto-selects
|
| 1202 |
-
by sm_arch and raises if no pin exists."""
|
| 1203 |
if pinned is None:
|
| 1204 |
arch = current["device"]["sm_arch"]
|
| 1205 |
matches = [k for k, v in _PINNED.items()
|
|
@@ -1222,16 +1757,19 @@ def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
|
| 1222 |
"rel": round(rel, 4)})
|
| 1223 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1224 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
|
| 1229 |
|
| 1230 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1231 |
device="cuda"):
|
| 1232 |
-
"""Run the shipped MK_STAMP_BLOCK example
|
| 1233 |
-
compute, global store) and decode its stamps; a template for
|
| 1234 |
-
instrumenting a candidate."""
|
| 1235 |
props = torch.cuda.get_device_properties(device)
|
| 1236 |
if blocks is None:
|
| 1237 |
blocks = props.multi_processor_count
|
|
@@ -1244,11 +1782,7 @@ def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
|
| 1244 |
|
| 1245 |
|
| 1246 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1247 |
-
"""Decode MK_STAMP
|
| 1248 |
-
microseconds under the measured (or provided) SM clock. A 1-D buffer
|
| 1249 |
-
(MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
|
| 1250 |
-
buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
|
| 1251 |
-
phase, with blocks whose stamps stayed zero excluded."""
|
| 1252 |
if clock_ghz is None:
|
| 1253 |
clock_ghz = measure_clock(stamps.device)
|
| 1254 |
|
|
@@ -1284,3 +1818,104 @@ def read_stamps(stamps, labels=None, clock_ghz=None):
|
|
| 1284 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1285 |
"blocks_reporting": int(live.sum().item()),
|
| 1286 |
"blocks_total": int(live.numel())}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Measuring instrument for the kernel-design loop: measured device facts, throttle-aware timing, adversarial correctness comparison, all as loadable ops returning plain data."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import math
|
| 3 |
import os
|
| 4 |
import time
|
|
|
|
| 8 |
try:
|
| 9 |
from ._ops import ops
|
| 10 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 11 |
+
# children re-import the package; a source checkout rebuilds the op table from the cached JIT extension
|
|
|
|
|
|
|
| 12 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 13 |
if not _root:
|
| 14 |
raise
|
|
|
|
| 30 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 31 |
_PINNED = {}
|
| 32 |
|
| 33 |
+
__all__ = ["probe_device", "probe_all_devices", "bench", "compare", "judge",
|
| 34 |
+
"order_shuffles", "fuzz_shapes", "sweep", "alloc_stamps",
|
| 35 |
+
"read_stamps", "stamped_demo", "measure_clock", "ulp_diff",
|
| 36 |
+
"dossier_drift", "pinned_dossiers", "mma_tile_check", "schema",
|
| 37 |
+
"probe_capturable", "ops"]
|
| 38 |
+
|
| 39 |
+
_REPO_ID = "phanerozoic/metakernel"
|
| 40 |
+
_REPO_VERSION = 1
|
| 41 |
+
_PROBE_SCHEMA = 2
|
| 42 |
+
|
| 43 |
+
# kind id, C-tile M*N, nominal K, nominal K-lanes per unrolled loop iteration
|
| 44 |
_MMA_KINDS = {
|
| 45 |
+
"fp16": (0, 16 * 16, 16, 64.0),
|
| 46 |
+
"bf16": (1, 16 * 16, 16, 64.0),
|
| 47 |
+
"tf32": (2, 16 * 16, 8, 32.0),
|
| 48 |
+
"int8": (3, 16 * 16, 16, 64.0),
|
| 49 |
+
"fp8_e4m3": (4, 16 * 8, 32, 256.0),
|
| 50 |
+
"fp16_sparse_2_4": (5, 16 * 8, 32, 128.0),
|
| 51 |
+
"fp8_e5m2": (6, 16 * 8, 32, 256.0),
|
| 52 |
}
|
| 53 |
|
| 54 |
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def _events(fn, reps):
|
| 60 |
+
"""Time reps calls of fn with one cudaEvent pair each; returns a ms list."""
|
| 61 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 62 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 63 |
for s, e in zip(starts, stops):
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 72 |
+
"""Effective SM clock in GHz from clock64 ticks over wall time under an FMA spin."""
|
|
|
|
| 73 |
props = torch.cuda.get_device_properties(device)
|
| 74 |
if blocks is None:
|
| 75 |
blocks = props.multi_processor_count
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
def _sattolo_ring(n, device, generator=None):
|
| 105 |
+
"""Random single-cycle permutation as an int32 next-pointer table."""
|
|
|
|
| 106 |
perm = torch.randperm(n, generator=generator)
|
| 107 |
ring = torch.empty(n, dtype=torch.int32)
|
| 108 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
|
|
| 110 |
|
| 111 |
|
| 112 |
def _smi_settings(index):
|
| 113 |
+
"""ECC mode, driver, power limit via nvidia-smi; None fields when unavailable."""
|
|
|
|
| 114 |
import subprocess
|
| 115 |
try:
|
| 116 |
out = subprocess.run(
|
|
|
|
| 130 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 131 |
|
| 132 |
|
| 133 |
+
def _unravel(flat, shape):
|
| 134 |
+
"""Multi-dimensional index for a flat index into shape."""
|
| 135 |
+
if not shape:
|
| 136 |
+
return []
|
| 137 |
+
out = []
|
| 138 |
+
for s in reversed(list(shape)):
|
| 139 |
+
out.append(int(flat % s))
|
| 140 |
+
flat //= s
|
| 141 |
+
return list(reversed(out))
|
| 142 |
+
|
| 143 |
+
|
| 144 |
def mma_tile_check(device="cuda", trials=8):
|
| 145 |
+
"""Random small-integer m16n16k16 tiles through the wmma path, compared exactly to the reference GEMM."""
|
|
|
|
|
|
|
| 146 |
worst = 0.0
|
| 147 |
for t in range(trials):
|
| 148 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
|
|
| 160 |
"trials": trials}
|
| 161 |
|
| 162 |
|
| 163 |
+
def _tenancy_canary(device, rel_tol=0.10, attempts=3):
|
| 164 |
+
"""Fixed-cost triad timed in paired rounds; busy only when every attempt disagrees."""
|
| 165 |
+
n4 = 4 << 20
|
| 166 |
+
a = torch.rand(n4 * 4, device=device)
|
| 167 |
+
b = torch.rand(n4 * 4, device=device)
|
| 168 |
+
c = torch.empty(n4 * 4, device=device)
|
| 169 |
+
ops.mk_triad(a, b, c, 4, 1.5)
|
| 170 |
+
rels = []
|
| 171 |
+
for _ in range(attempts):
|
| 172 |
+
r1 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 173 |
+
r2 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 174 |
+
rel = abs(r1 - r2) / min(r1, r2)
|
| 175 |
+
rels.append(round(rel, 4))
|
| 176 |
+
if rel <= rel_tol:
|
| 177 |
+
return False, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 178 |
+
return True, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _cache_dir():
|
| 182 |
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~/.cache")
|
| 183 |
+
d = os.path.join(base, "metakernel")
|
| 184 |
+
os.makedirs(d, exist_ok=True)
|
| 185 |
+
return d
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _cache_path(device, quick):
|
| 189 |
+
import re
|
| 190 |
+
props = torch.cuda.get_device_properties(device)
|
| 191 |
+
smi = _smi_settings(torch.device(device).index or 0)
|
| 192 |
+
slug = re.sub(r"[^a-z0-9]+", "", props.name.lower())[:24]
|
| 193 |
+
key = "sm%d%d_%s_drv%s_cu%s_q%d_s%d" % (
|
| 194 |
+
props.major, props.minor, slug, smi["driver"] or "na",
|
| 195 |
+
torch.version.cuda, int(quick), _PROBE_SCHEMA)
|
| 196 |
+
return os.path.join(_cache_dir(), key + ".json")
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _mma_calibrate(kind_id, sink):
|
| 200 |
+
"""Executed K-lanes per loop iteration from a sub-saturation accumulator read-back, with a linearity check."""
|
| 201 |
+
gains = []
|
| 202 |
+
for cal in (4, 32):
|
| 203 |
+
sink[1] = 0.0
|
| 204 |
+
ops.mk_mma(kind_id, 1, 1, cal, sink)
|
| 205 |
+
_sync()
|
| 206 |
+
gains.append(float(sink[1].item()) / cal)
|
| 207 |
+
linear = gains[0] > 0 and abs(gains[1] - gains[0]) / gains[0] < 0.02
|
| 208 |
+
return gains[0], linear
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _fp8_accum_stall(kind_id, sink, k_exec):
|
| 212 |
+
"""Growth curve of the fp8 accumulator; a stall reveals the effective accumulate mantissa width."""
|
| 213 |
+
vals = []
|
| 214 |
+
for it in (32, 64, 128, 256, 512):
|
| 215 |
+
sink[1] = 0.0
|
| 216 |
+
ops.mk_mma(kind_id, 1, 1, it, sink)
|
| 217 |
+
_sync()
|
| 218 |
+
vals.append(float(sink[1].item()) / 2.0)
|
| 219 |
+
if vals[-1] == vals[-2] and vals[-1] < vals[0] * 16 * 0.99:
|
| 220 |
+
bits = int(round(math.log2(vals[-1] / (2.0 * k_exec))))
|
| 221 |
+
return {"stall_element_value": vals[-1],
|
| 222 |
+
"effective_accum_mantissa_bits": bits}
|
| 223 |
+
return {"stall_element_value": None,
|
| 224 |
+
"effective_accum_mantissa_bits": None}
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def probe_device(device="cuda", quick=False, cached=True,
|
| 228 |
+
tenancy_check=True):
|
| 229 |
+
"""Empirical device dossier; every entry measured by a dedicated probe kernel and stamped with its SM clock."""
|
| 230 |
+
cpath = _cache_path(device, quick)
|
| 231 |
+
if cached and os.path.exists(cpath):
|
| 232 |
+
import json
|
| 233 |
+
return json.load(open(cpath, encoding="utf-8"))
|
| 234 |
+
if tenancy_check:
|
| 235 |
+
busy, ev = _tenancy_canary(device)
|
| 236 |
+
if busy:
|
| 237 |
+
raise RuntimeError("device_busy: canary rounds disagree %s; "
|
| 238 |
+
"idle the device or pass "
|
| 239 |
+
"tenancy_check=False" % ev)
|
| 240 |
props = torch.cuda.get_device_properties(device)
|
| 241 |
sm = props.multi_processor_count
|
| 242 |
l2 = props.L2_cache_size
|
|
|
|
| 249 |
"vram_bytes": props.total_memory,
|
| 250 |
"torch": torch.__version__,
|
| 251 |
"cuda_runtime": torch.version.cuda,
|
| 252 |
+
# instrument settings recorded because readings depend on them
|
|
|
|
| 253 |
**_smi_settings(torch.device(device).index or 0),
|
| 254 |
}
|
| 255 |
}
|
|
|
|
| 274 |
}
|
| 275 |
clk = max(traj)
|
| 276 |
|
| 277 |
+
# ---- device-memory bandwidth per access width plus read/write/gather
|
| 278 |
+
n4 = 16 << 20
|
|
|
|
| 279 |
if props.total_memory < 4 << 30:
|
| 280 |
n4 = 4 << 20
|
| 281 |
a = torch.rand(n4 * 4, device=device)
|
|
|
|
| 299 |
dtype=torch.int32).to(device)
|
| 300 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 301 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
|
|
|
| 302 |
hbm["random_gather_16B"] = round(
|
| 303 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 304 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
|
|
| 309 |
"so its nominal number is the achievable rate for "
|
| 310 |
"random access, not a line-efficiency claim")
|
| 311 |
|
| 312 |
+
# ---- L2 bandwidth over an L2-resident working set
|
| 313 |
ws = max(l2 // 4, 1 << 20)
|
| 314 |
+
nl2 = ws // 16
|
| 315 |
al = a[:nl2 * 4].contiguous()
|
| 316 |
bl = b[:nl2 * 4].contiguous()
|
| 317 |
cl = c[:nl2 * 4].contiguous()
|
|
|
|
| 326 |
"medium", "first pass streams from HBM; amortized "
|
| 327 |
f"over {passes} passes")
|
| 328 |
|
| 329 |
+
# ---- latencies: min of 3 chases per level so a co-tenant burst cannot land in the dossier
|
|
|
|
|
|
|
| 330 |
lat = {}
|
| 331 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
+
def chase(op, ring, warm_hops, hops, extra=None):
|
| 334 |
+
best = None
|
| 335 |
+
for _ in range(3):
|
| 336 |
+
if extra is None:
|
| 337 |
+
op(ring, warm_hops, out64)
|
| 338 |
+
_sync()
|
| 339 |
+
op(ring, hops, out64)
|
| 340 |
+
else:
|
| 341 |
+
op(ring, extra, warm_hops, out64)
|
| 342 |
+
_sync()
|
| 343 |
+
op(ring, extra, hops, out64)
|
| 344 |
+
_sync()
|
| 345 |
+
t = int(out64[0].item()) / hops
|
| 346 |
+
best = t if best is None else min(best, t)
|
| 347 |
+
return best
|
|
|
|
|
|
|
| 348 |
|
| 349 |
+
ring_s = _sattolo_ring(8192, device)
|
| 350 |
+
lat["smem_cycles"] = round(
|
| 351 |
+
chase(lambda r, h, o: ops.mk_chase_shared(r, h, o), ring_s,
|
| 352 |
+
1 << 14, 1 << 16), 1)
|
| 353 |
+
ring_l1 = _sattolo_ring((16 << 10) // 4, device)
|
| 354 |
+
lat["l1_cycles"] = round(
|
| 355 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l1,
|
| 356 |
+
ring_l1.numel(), 1 << 15), 1)
|
| 357 |
+
ring_l2 = _sattolo_ring((2 << 20) // 4, device)
|
| 358 |
+
lat["l2_cycles"] = round(
|
| 359 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l2,
|
| 360 |
+
ring_l2.numel(), 1 << 14), 1)
|
| 361 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 362 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 363 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 364 |
+
lat["dram_cycles"] = round(
|
| 365 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_h,
|
| 366 |
+
1 << 14, hops_h), 1)
|
|
|
|
|
|
|
| 367 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 368 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 369 |
+
"Sattolo-cycle pointer chase, min of 3 runs; 16 "
|
| 370 |
+
"KB ring for L1, 2 MB TLB-resident ring for L2, "
|
| 371 |
+
"self-evicting chain (2x L2 in touched lines) "
|
| 372 |
+
"for HBM", idle_clk, "high",
|
| 373 |
"smem includes index arithmetic; HBM includes "
|
| 374 |
"residual TLB effects at large footprints")
|
| 375 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 376 |
|
| 377 |
+
# ---- fp32/fp64 FMA and tensor-core throughput
|
| 378 |
blocks = sm * 4
|
| 379 |
iters = 20_000 if quick else 60_000
|
| 380 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 381 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 382 |
reps))
|
| 383 |
+
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 384 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 385 |
"register-resident FMA chains", clk, "high")
|
| 386 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
|
|
| 392 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 393 |
"register-resident double FMA chains (the DFMA "
|
| 394 |
"pipe)", clk, "high")
|
| 395 |
+
|
| 396 |
+
# ---- ILP ladder: 1/2/4 chains at low occupancy separates latency-bound from pipe-bound
|
| 397 |
+
ilp = {}
|
| 398 |
+
it_ilp = 20_000 if quick else 60_000
|
| 399 |
+
for ch in (1, 2, 4):
|
| 400 |
+
ops.mk_fma_ilp(ch, sm, 64, it_ilp, sink)
|
| 401 |
+
ms = _median(_events(
|
| 402 |
+
lambda c2=ch: ops.mk_fma_ilp(c2, sm, 64, it_ilp, sink), reps))
|
| 403 |
+
ilp[f"ilp{ch}"] = round(
|
| 404 |
+
sm * 64 * it_ilp * 16 * 2 * ch / (ms * 1e-3) / 1e12, 2)
|
| 405 |
+
ilp["ratio_4_1"] = round(ilp["ilp4"] / ilp["ilp1"], 2)
|
| 406 |
+
d["fma_ilp"] = _entry(ilp, "TFLOP/s",
|
| 407 |
+
"1/2/4 independent dependent FMA chains per "
|
| 408 |
+
"thread at one block of 64 threads per SM; a "
|
| 409 |
+
"ratio near 4 means latency-bound at that "
|
| 410 |
+
"occupancy", clk, "high")
|
| 411 |
+
|
| 412 |
mma = {}
|
| 413 |
mma_klanes = {}
|
| 414 |
+
fp8_accum = {}
|
| 415 |
+
for name, (kind, mn, k_nom, nom_lanes) in _MMA_KINDS.items():
|
| 416 |
it = 30_000 if quick else 100_000
|
| 417 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 418 |
if r != 0:
|
| 419 |
mma[name] = "unsupported"
|
| 420 |
continue
|
| 421 |
_sync()
|
| 422 |
+
gain, linear = _mma_calibrate(kind, sink)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
mma_klanes[name] = {
|
| 424 |
+
"executed_k_per_iter": round(gain, 2),
|
| 425 |
+
"vs_nominal": round(gain / nom_lanes, 3),
|
| 426 |
+
"calibration_linear": linear,
|
| 427 |
}
|
| 428 |
+
if name.startswith("fp8"):
|
| 429 |
+
fp8_accum[name] = _fp8_accum_stall(kind, sink, k_nom)
|
| 430 |
ms = _median(_events(
|
| 431 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 432 |
+
tflops = blocks * 8 * it * 2.0 * mn * gain / (ms * 1e-3) / 1e12
|
| 433 |
+
if name == "fp16_sparse_2_4":
|
| 434 |
+
mma[name] = {"executed": round(tflops, 1),
|
| 435 |
+
"dense_equivalent": round(2 * tflops, 1)}
|
| 436 |
+
else:
|
| 437 |
+
mma[name] = round(tflops, 1)
|
| 438 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 439 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 440 |
+
"2:4-sparse fp16 via inline PTX m16n8k32); flops "
|
| 441 |
+
"derive from a sub-saturation accumulator read-back "
|
|
|
|
| 442 |
"with every fragment element observed, never the "
|
| 443 |
+
"declared loop shape; sparse reports executed and "
|
| 444 |
+
"dense-equivalent flops",
|
| 445 |
clk, "medium",
|
| 446 |
"dense-tile rate without memory traffic; real "
|
| 447 |
"kernels see this only when staging hides behind it")
|
| 448 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
| 449 |
+
if fp8_accum:
|
| 450 |
+
d["mma"]["fp8_accum"] = fp8_accum
|
| 451 |
|
| 452 |
+
# ---- fed MMA: smem-staged fp16 tiles under cp.async double buffering
|
| 453 |
+
if props.major >= 8:
|
| 454 |
+
gbuf = torch.ones(16 << 20, dtype=torch.float16, device=device)
|
| 455 |
+
sink[1] = 0.0
|
| 456 |
+
r = ops.mk_mma_fed(gbuf, 1, 1, 64, sink)
|
| 457 |
+
if r == 0:
|
| 458 |
+
_sync()
|
| 459 |
+
fed_gain = float(sink[1].item()) / 64
|
| 460 |
+
it_fed = 6_000 if quick else 20_000
|
| 461 |
+
ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink)
|
| 462 |
+
ms = _median(_events(
|
| 463 |
+
lambda: ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink),
|
| 464 |
+
reps))
|
| 465 |
+
fed = sm * 2 * 8 * it_fed * 2.0 * 256 * fed_gain / \
|
| 466 |
+
(ms * 1e-3) / 1e12
|
| 467 |
+
d["mma_fed"] = _entry(
|
| 468 |
+
round(fed, 1), "TFLOP/s",
|
| 469 |
+
"m16n16k16 fp16 tiles fed from an L2-resident buffer "
|
| 470 |
+
"through cp.async double-buffered shared memory; "
|
| 471 |
+
"accumulator-verified", clk, "high",
|
| 472 |
+
"the achieved counterpart to the register peak: staging "
|
| 473 |
+
"exposed, one dependent accumulator chain per warp")
|
| 474 |
+
del gbuf
|
| 475 |
+
|
| 476 |
+
# ---- shared-memory bandwidth and the bank-conflict stride sweep
|
| 477 |
it_sm = 30_000 if quick else 100_000
|
| 478 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 479 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
|
|
| 483 |
"block-resident read+write streaming (8 B per "
|
| 484 |
"thread-iteration), aggregated across SMs", clk,
|
| 485 |
"medium",
|
| 486 |
+
"bank-conflict-light access pattern; the stride "
|
| 487 |
+
"sweep below prices the conflicted case")
|
| 488 |
+
strides = (1, 8, 32) if quick else (1, 2, 4, 8, 16, 32)
|
| 489 |
+
it_st = 8_000 if quick else 20_000
|
| 490 |
+
st_ms = {}
|
| 491 |
+
for s2 in strides:
|
| 492 |
+
ops.mk_smem_stride(s2, blocks, 256, it_st, sink)
|
| 493 |
+
st_ms[s2] = _median(_events(
|
| 494 |
+
lambda s3=s2: ops.mk_smem_stride(s3, blocks, 256, it_st, sink),
|
| 495 |
+
reps))
|
| 496 |
+
d["smem_conflict_penalty"] = _entry(
|
| 497 |
+
{f"stride_{s2}": round(st_ms[s2] / st_ms[1], 2) for s2 in strides},
|
| 498 |
+
"x (time ratio vs stride 1)",
|
| 499 |
+
"dependent read-FMA-write per thread at warp-lane bank distance "
|
| 500 |
+
"`stride`; stride 32 puts every lane in one bank", clk, "high",
|
| 501 |
+
"the direct conflict measurement that disambiguates a "
|
| 502 |
+
"bank-conflict signature from an ILP stall")
|
| 503 |
|
| 504 |
# ---- PCIe / host transfer bandwidth
|
| 505 |
nbytes = 256 << 20
|
|
|
|
| 530 |
"d2d counts one direction of traffic")
|
| 531 |
del host_pin, host_pag, dev_t, dev_u
|
| 532 |
|
| 533 |
+
# ---- atomic contention curve
|
|
|
|
|
|
|
| 534 |
n_thr = blocks * 256
|
| 535 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 536 |
at = {"curve_slots": {}}
|
|
|
|
| 540 |
buf.zero_()
|
| 541 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 542 |
ms = _median(_events(
|
| 543 |
+
lambda s4=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s4),
|
| 544 |
reps))
|
| 545 |
at["curve_slots"][str(slots)] = round(
|
| 546 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
|
|
| 553 |
"of distinct global addresses from 1 (full "
|
| 554 |
"contention) to one per thread", clk, "high")
|
| 555 |
|
| 556 |
+
# ---- launch overhead: torch-dispatch path and the raw C++ path
|
| 557 |
anchor = sink
|
| 558 |
n_launch = 500 if quick else 2000
|
| 559 |
_sync()
|
|
|
|
| 568 |
ops.mk_empty(anchor)
|
| 569 |
_sync()
|
| 570 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
| 571 |
+
raw_q = float(ops.mk_launch_raw(n_launch, 0, anchor))
|
| 572 |
+
raw_rt = float(ops.mk_launch_raw(n_rt, 1, anchor))
|
| 573 |
d["launch_overhead"] = _entry(
|
| 574 |
+
{"torch_queued_us": round(queued, 2),
|
| 575 |
+
"torch_round_trip_us": round(rt, 2),
|
| 576 |
+
"raw_queued_us": round(raw_q / n_launch * 1e6, 2)
|
| 577 |
+
if raw_q >= 0 else None,
|
| 578 |
+
"raw_round_trip_us": round(raw_rt / n_rt * 1e6, 2)
|
| 579 |
+
if raw_rt >= 0 else None},
|
| 580 |
+
"us", "empty-kernel trains; torch path per-launch through op "
|
| 581 |
+
"dispatch, raw path looped inside one binding call", clk,
|
| 582 |
+
"medium", "host-state-dependent; the torch-minus-raw gap is the "
|
| 583 |
+
"dispatch cost a torch-launched candidate pays")
|
| 584 |
|
| 585 |
# ---- grid barrier
|
| 586 |
bar_iters = 500 if quick else 2000
|
|
|
|
| 596 |
"unavailable or grid too large", clk,
|
| 597 |
"high")
|
| 598 |
|
| 599 |
+
# ---- occupancy API table plus timed knees
|
|
|
|
|
|
|
| 600 |
occ = {}
|
| 601 |
for threads in (128, 256, 512, 1024):
|
| 602 |
row = {}
|
|
|
|
| 617 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 618 |
knee_iters, sink)
|
| 619 |
ms = _median(_events(
|
| 620 |
+
lambda t=threads, s5=smem_kb: ops.mk_fma_dep(
|
| 621 |
+
knee_blocks, t, s5 * 1024, knee_iters, sink), reps))
|
| 622 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 623 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 624 |
knees[f"t{threads}"] = row
|
|
|
|
| 627 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 628 |
"the cliff marks where occupancy stops covering latency",
|
| 629 |
clk, "high", "timing-derived counterpart to the API table above")
|
| 630 |
+
if cached:
|
| 631 |
+
import json
|
| 632 |
+
json.dump(d, open(cpath, "w", encoding="utf-8"))
|
| 633 |
return d
|
| 634 |
|
| 635 |
|
| 636 |
+
def probe_all_devices(quick=True, cached=True, tenancy_check=True):
|
| 637 |
+
"""One dossier per visible device plus a pairwise P2P bandwidth matrix."""
|
| 638 |
+
n = torch.cuda.device_count()
|
| 639 |
+
devices = {}
|
| 640 |
+
for i in range(n):
|
| 641 |
+
devices[i] = probe_device(f"cuda:{i}", quick=quick, cached=cached,
|
| 642 |
+
tenancy_check=tenancy_check)
|
| 643 |
+
p2p = {}
|
| 644 |
+
if n > 1:
|
| 645 |
+
nbytes = 64 << 20
|
| 646 |
+
for i in range(n):
|
| 647 |
+
for j in range(n):
|
| 648 |
+
if i == j:
|
| 649 |
+
continue
|
| 650 |
+
src = torch.rand(nbytes // 4, device=f"cuda:{i}")
|
| 651 |
+
dst = torch.empty(nbytes // 4, device=f"cuda:{j}")
|
| 652 |
+
dst.copy_(src)
|
| 653 |
+
torch.cuda.synchronize(i)
|
| 654 |
+
torch.cuda.synchronize(j)
|
| 655 |
+
t0 = time.perf_counter()
|
| 656 |
+
for _ in range(5):
|
| 657 |
+
dst.copy_(src)
|
| 658 |
+
torch.cuda.synchronize(i)
|
| 659 |
+
torch.cuda.synchronize(j)
|
| 660 |
+
ms = (time.perf_counter() - t0) / 5 * 1e3
|
| 661 |
+
p2p[f"{i}->{j}"] = {
|
| 662 |
+
"gbs": round(nbytes / (ms * 1e-3) / 1e9, 1),
|
| 663 |
+
"peer_access": bool(
|
| 664 |
+
torch.cuda.can_device_access_peer(i, j)),
|
| 665 |
+
}
|
| 666 |
+
del src, dst
|
| 667 |
+
return {"devices": devices, "p2p": p2p}
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
_CAPTURE_PROBE = r"""
|
| 671 |
+
import pickle, sys, torch
|
| 672 |
+
fn = pickle.load(open(sys.argv[1], "rb"))
|
| 673 |
+
for _ in range(3):
|
| 674 |
+
fn()
|
| 675 |
+
torch.cuda.synchronize()
|
| 676 |
+
try:
|
| 677 |
+
g = torch.cuda.CUDAGraph()
|
| 678 |
+
with torch.cuda.graph(g):
|
| 679 |
+
fn()
|
| 680 |
+
print("capturable", flush=True)
|
| 681 |
+
except Exception as e:
|
| 682 |
+
print("uncapturable: %s: %s" % (type(e).__name__, str(e)[:120]),
|
| 683 |
+
flush=True)
|
| 684 |
+
"""
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def probe_capturable(fn, timeout_s=180):
|
| 688 |
+
"""Test CUDA-graph capturability of fn in a child process, so a capture-wedged RNG never reaches the caller."""
|
| 689 |
+
import pickle
|
| 690 |
+
import subprocess
|
| 691 |
+
import sys as _sys
|
| 692 |
+
import tempfile
|
| 693 |
+
try:
|
| 694 |
+
blob = pickle.dumps(fn)
|
| 695 |
+
except Exception: # noqa: BLE001
|
| 696 |
+
return {"capturable": None,
|
| 697 |
+
"reason": "fn is not picklable; probe skipped (define fn "
|
| 698 |
+
"at module top level to enable it)"}
|
| 699 |
+
path = os.path.join(tempfile.mkdtemp(prefix="mk_cap_"), "fn.pkl")
|
| 700 |
+
with open(path, "wb") as f:
|
| 701 |
+
f.write(blob)
|
| 702 |
+
try:
|
| 703 |
+
r = subprocess.run([_sys.executable, "-c", _CAPTURE_PROBE, path],
|
| 704 |
+
capture_output=True, text=True,
|
| 705 |
+
timeout=timeout_s)
|
| 706 |
+
except Exception as e: # noqa: BLE001
|
| 707 |
+
return {"capturable": None,
|
| 708 |
+
"reason": f"probe child failed: {type(e).__name__}"}
|
| 709 |
+
out = (r.stdout or "").strip()
|
| 710 |
+
if out == "capturable":
|
| 711 |
+
return {"capturable": True, "reason": ""}
|
| 712 |
+
if out.startswith("uncapturable"):
|
| 713 |
+
return {"capturable": False, "reason": out}
|
| 714 |
+
return {"capturable": None,
|
| 715 |
+
"reason": "probe child exit %s: %s" % (
|
| 716 |
+
r.returncode, (r.stderr or "")[-200:])}
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
def _bench_verdict(rep):
|
| 720 |
+
parts = ["median %.4g ms" % rep["median_ms"]]
|
| 721 |
+
if "achieved_tflops" in rep:
|
| 722 |
+
parts.append("%.4g TFLOP/s" % rep["achieved_tflops"])
|
| 723 |
+
elif "achieved_gbs" in rep:
|
| 724 |
+
parts.append("%.4g GB/s" % rep["achieved_gbs"])
|
| 725 |
+
if "roofline" in rep:
|
| 726 |
+
parts.append("%.0f%% of %s roof" % (rep["roofline"]["pct_of_roof"],
|
| 727 |
+
rep["roofline"]["bound"]))
|
| 728 |
+
if rep["rejected_throttled"]:
|
| 729 |
+
parts.append("%d/%d rejected (%s)" % (
|
| 730 |
+
rep["rejected_throttled"], rep["samples"],
|
| 731 |
+
rep["rejection_basis"]))
|
| 732 |
+
if rep.get("no_clean_window"):
|
| 733 |
+
parts.append("no clean window: the whole run was throttled")
|
| 734 |
+
if rep.get("alloc_growth_bytes_per_call"):
|
| 735 |
+
parts.append("fn allocates %d B/call" %
|
| 736 |
+
rep["alloc_growth_bytes_per_call"])
|
| 737 |
+
if rep.get("host_overhead_suspected"):
|
| 738 |
+
parts.append("fn appears to sync or do host work inside")
|
| 739 |
+
return "; ".join(parts)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 743 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 744 |
+
graph_probe_isolated=False, device="cuda"):
|
| 745 |
+
"""Median-of-N cudaEvent timing with throttle rejection, roofline placement, and footgun detection."""
|
| 746 |
+
for name, v in (("iters", iters), ("warmup", warmup)):
|
| 747 |
+
if not isinstance(v, int) or v < (1 if name == "iters" else 0):
|
| 748 |
+
raise ValueError(f"{name} must be a non-negative int; "
|
| 749 |
+
f"got {v!r}")
|
| 750 |
+
for name, v in (("bytes", bytes), ("flops", flops)):
|
| 751 |
+
if v is not None and (not isinstance(v, (int, float)) or v <= 0):
|
| 752 |
+
raise ValueError(f"{name} must be a positive number counting "
|
| 753 |
+
f"{name} per call of fn; got {v!r}")
|
|
|
|
| 754 |
for _ in range(warmup):
|
| 755 |
fn()
|
| 756 |
_sync()
|
| 757 |
+
# footgun probes: per-call allocation growth and hidden host work
|
| 758 |
+
alloc0 = torch.cuda.memory_allocated(device)
|
| 759 |
+
fn()
|
| 760 |
+
fn()
|
| 761 |
+
_sync()
|
| 762 |
+
alloc_growth = max(0, (torch.cuda.memory_allocated(device) - alloc0)
|
| 763 |
+
// 2)
|
| 764 |
+
# a burst of enqueues that leaves the stream idle means fn drains it; only trustworthy above the launch-overhead floor
|
| 765 |
+
for _ in range(16):
|
| 766 |
+
fn()
|
| 767 |
+
stream_idle = bool(torch.cuda.current_stream(device).query())
|
| 768 |
+
_sync()
|
| 769 |
+
t0 = time.perf_counter()
|
| 770 |
+
pre = _events(fn, 4)
|
| 771 |
+
wall_ms = (time.perf_counter() - t0) * 1e3
|
| 772 |
+
ev_ms = sum(pre)
|
| 773 |
+
host_suspect = (stream_idle and ev_ms / 4 > 0.05) or \
|
| 774 |
+
wall_ms > ev_ms * 1.5 + 2.0
|
| 775 |
+
|
| 776 |
graph_state = "off"
|
| 777 |
graph_reason = ""
|
| 778 |
+
if graph and graph_probe_isolated:
|
| 779 |
+
cap = probe_capturable(fn)
|
| 780 |
+
if cap["capturable"] is False:
|
| 781 |
+
graph = False
|
| 782 |
+
graph_state = "fallback_eager"
|
| 783 |
+
graph_reason = "isolated probe: " + cap["reason"]
|
| 784 |
if graph:
|
| 785 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 786 |
try:
|
|
|
|
| 795 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 796 |
graph_state = "fallback_eager"
|
| 797 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 798 |
+
# drain the invalidated stream; a failed capture can wedge the device RNG on some torch versions
|
|
|
|
|
|
|
|
|
|
| 799 |
rng_ok = True
|
| 800 |
for _attempt in range(5):
|
| 801 |
try:
|
|
|
|
| 825 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 826 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 827 |
best_clk = max(clocks)
|
| 828 |
+
|
| 829 |
+
# rejection basis: memory-bound work is judged on its own rate, not the SM clock
|
| 830 |
+
ridge_memory = False
|
| 831 |
+
if dossier is not None and bytes and flops:
|
| 832 |
+
bw0 = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 833 |
+
mma_v0 = dossier["mma"]["value"].get(dtype)
|
| 834 |
+
if isinstance(mma_v0, dict):
|
| 835 |
+
mma_v0 = mma_v0.get("dense_equivalent")
|
| 836 |
+
peak0 = (mma_v0 if isinstance(mma_v0, (int, float))
|
| 837 |
+
else dossier["fma_f32"]["value"]) * 1e12
|
| 838 |
+
ridge_memory = (flops / bytes) * bw0 < peak0
|
| 839 |
+
basis = "bandwidth_consistency" if (
|
| 840 |
+
bytes is not None and (flops is None or ridge_memory)) \
|
| 841 |
+
else "sm_clock"
|
| 842 |
+
|
| 843 |
flat = []
|
| 844 |
+
kept = []
|
| 845 |
+
rejected = 0
|
| 846 |
+
if basis == "sm_clock":
|
| 847 |
+
for group, ci in samples:
|
| 848 |
+
window_ok = (clocks[ci] >= clock_guard * best_clk and
|
| 849 |
+
clocks[min(ci + 1, len(clocks) - 1)] >=
|
| 850 |
+
clock_guard * best_clk)
|
| 851 |
+
for ms in group:
|
| 852 |
+
flat.append(ms)
|
| 853 |
+
if window_ok:
|
| 854 |
+
kept.append(ms)
|
| 855 |
+
else:
|
| 856 |
+
rejected += 1
|
| 857 |
+
else:
|
| 858 |
+
for group, _ci in samples:
|
| 859 |
+
flat.extend(group)
|
| 860 |
+
floor = sorted(flat)[max(0, int(0.05 * len(flat)) - 1)]
|
| 861 |
+
for ms in flat:
|
| 862 |
+
if ms <= floor * 1.15:
|
| 863 |
kept.append(ms)
|
| 864 |
else:
|
| 865 |
rejected += 1
|
| 866 |
+
all_rejected = not kept
|
| 867 |
+
if all_rejected:
|
| 868 |
kept = flat
|
| 869 |
med = _median(kept)
|
| 870 |
ks = sorted(kept)
|
| 871 |
n_k = len(ks)
|
| 872 |
+
# order-statistic 95% CI on the median; degenerate at tiny n
|
|
|
|
| 873 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 874 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 875 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
|
|
| 884 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 885 |
"samples": len(flat),
|
| 886 |
"rejected_throttled": rejected,
|
| 887 |
+
"rejection_basis": basis,
|
| 888 |
"graph_captured": graph_state == "captured",
|
| 889 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 890 |
"worst": round(min(clocks), 3)},
|
| 891 |
}
|
| 892 |
+
no_clean = all_rejected
|
| 893 |
+
if basis == "sm_clock" and dossier is not None:
|
| 894 |
+
ref_clk = dossier["clock"]["max_ghz"]
|
| 895 |
+
if best_clk < clock_guard * ref_clk:
|
| 896 |
+
no_clean = True
|
| 897 |
+
if no_clean:
|
| 898 |
+
rep["no_clean_window"] = True
|
| 899 |
if graph_reason:
|
| 900 |
rep["graph_fallback_reason"] = graph_reason
|
| 901 |
+
if alloc_growth:
|
| 902 |
+
rep["alloc_growth_bytes_per_call"] = int(alloc_growth)
|
| 903 |
+
if host_suspect:
|
| 904 |
+
rep["host_overhead_suspected"] = True
|
| 905 |
+
rep["host_wall_over_event"] = round(wall_ms / max(ev_ms, 1e-9), 2)
|
| 906 |
if bytes is not None:
|
| 907 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 908 |
if flops is not None:
|
|
|
|
| 912 |
if dossier is not None and bytes and flops:
|
| 913 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 914 |
mma_v = dossier["mma"]["value"].get(dtype)
|
| 915 |
+
if isinstance(mma_v, dict):
|
| 916 |
+
mma_v = mma_v.get("dense_equivalent")
|
| 917 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 918 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 919 |
ai = flops / bytes
|
|
|
|
| 924 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 925 |
"peak_dtype": dtype,
|
| 926 |
}
|
| 927 |
+
rep["verdict"] = _bench_verdict(rep)
|
| 928 |
return rep
|
| 929 |
|
| 930 |
|
| 931 |
def _to_ordered_int(t):
|
| 932 |
+
"""Monotone integer mapping of float bit patterns; differences are ULP distances."""
|
|
|
|
|
|
|
| 933 |
if t.dtype == torch.float32:
|
| 934 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 935 |
half = 1 << 31
|
|
|
|
| 945 |
|
| 946 |
|
| 947 |
def ulp_diff(a, b):
|
| 948 |
+
"""Elementwise ULP distance; both-NaN counts 0, one-sided NaN 2**62, integers by absolute difference."""
|
| 949 |
+
if a.dtype != b.dtype or a.shape != b.shape:
|
| 950 |
+
raise ValueError("ulp_diff needs matching dtype and shape; got "
|
| 951 |
+
f"{a.dtype}{tuple(a.shape)} vs "
|
| 952 |
+
f"{b.dtype}{tuple(b.shape)}; cast or reshape the "
|
| 953 |
+
"candidate to the reference layout")
|
| 954 |
if not a.is_floating_point():
|
| 955 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 956 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
|
|
| 967 |
|
| 968 |
@staticmethod
|
| 969 |
def last_dim():
|
| 970 |
+
"""Identical permutation of every arg's last dim: reduction order changes, value does not."""
|
|
|
|
|
|
|
| 971 |
def shuffle(args, seed):
|
| 972 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 973 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
|
|
| 977 |
|
| 978 |
@staticmethod
|
| 979 |
def matmul_k():
|
| 980 |
+
"""Consistent K permutation of (A [.., M, K], B [.., K, N]): product unchanged, accumulation order not."""
|
|
|
|
| 981 |
def shuffle(args, seed):
|
| 982 |
a_m, b_m = args[0], args[1]
|
| 983 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
|
|
| 991 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 992 |
|
| 993 |
|
| 994 |
+
def _compare_hints(trial):
|
| 995 |
+
hints = []
|
| 996 |
+
if trial.get("bias_one_sided_frac", 0) >= 0.9 and \
|
| 997 |
+
trial.get("frac_above_band", 0) > 0.5:
|
| 998 |
+
hints.append("systematic one-sided bias: check scale factors or "
|
| 999 |
+
"the accumulator dtype")
|
| 1000 |
+
if trial["nan_cand"] > trial["nan_ref"]:
|
| 1001 |
+
hints.append("candidate introduces NaN: check uninitialized "
|
| 1002 |
+
"memory or masking")
|
| 1003 |
+
nd = trial.get("first_divergent_nd")
|
| 1004 |
+
if nd and nd[-1] > 0 and any(nd[-1] % t == 0 for t in (16, 32, 64,
|
| 1005 |
+
128)):
|
| 1006 |
+
hints.append("divergence starts on a tile-aligned index: check "
|
| 1007 |
+
"boundary indexing")
|
| 1008 |
+
return hints
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def compare(ref, cand, gen=None, trials=8, order_shuffle=None,
|
| 1012 |
+
order_trials=8, ref_variants=None, ulp_tol=None, atol=None,
|
| 1013 |
+
rtol=None, band_margin=4.0, zero_atol=None, worst_k=8, seed=0,
|
| 1014 |
+
inputs=None, frac_above_tol=0.5):
|
| 1015 |
+
"""Adversarial correctness comparison with pointwise order bands, ULP evidence, and a one-sided-bias gate; the banded route cannot reject errors under band_margin*(band+2) ULP at order-stable elements."""
|
| 1016 |
+
if (gen is None) == (inputs is None):
|
| 1017 |
+
raise ValueError("pass exactly one of gen (a seed -> args "
|
| 1018 |
+
"callable) or inputs (a tuple of concrete "
|
| 1019 |
+
"tensors)")
|
| 1020 |
+
if inputs is not None:
|
| 1021 |
+
if isinstance(inputs, torch.Tensor):
|
| 1022 |
+
inputs = (inputs,)
|
| 1023 |
+
if not all(isinstance(x, torch.Tensor) for x in inputs):
|
| 1024 |
+
raise ValueError("inputs must be tensors; wrap scalars with "
|
| 1025 |
+
"torch.tensor first")
|
| 1026 |
+
fixed = tuple(inputs)
|
| 1027 |
+
gen = lambda seed: fixed # noqa: E731
|
| 1028 |
+
trials = 1
|
| 1029 |
worst = {"max_ulp": -1}
|
| 1030 |
total_hist = {}
|
| 1031 |
per_trial = []
|
| 1032 |
max_abs_seen = 0.0
|
| 1033 |
max_rel_seen = 0.0
|
| 1034 |
+
out_shapes = []
|
| 1035 |
for t in range(trials):
|
| 1036 |
args = gen(seed + t)
|
| 1037 |
+
if not isinstance(args, (tuple, list)):
|
| 1038 |
+
raise ValueError("gen(seed) must return a tuple of args; got "
|
| 1039 |
+
f"{type(args).__name__}; return (x,) for a "
|
| 1040 |
+
"single-argument op")
|
| 1041 |
r_outs = _as_outputs(ref(*args))
|
| 1042 |
c_outs = _as_outputs(cand(*args))
|
| 1043 |
+
if len(r_outs) != len(c_outs):
|
| 1044 |
+
raise ValueError(f"reference returned {len(r_outs)} outputs, "
|
| 1045 |
+
f"candidate {len(c_outs)}; make the arities "
|
| 1046 |
+
"match")
|
| 1047 |
+
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
| 1048 |
+
if r.dtype != c.dtype:
|
| 1049 |
+
raise ValueError(f"output {oi} dtype mismatch: ref "
|
| 1050 |
+
f"{r.dtype}, cand {c.dtype}; cast the "
|
| 1051 |
+
"candidate to the reference dtype")
|
| 1052 |
+
if r.shape != c.shape:
|
| 1053 |
+
raise ValueError(f"output {oi} shape mismatch: ref "
|
| 1054 |
+
f"{tuple(r.shape)}, cand "
|
| 1055 |
+
f"{tuple(c.shape)}")
|
| 1056 |
+
out_shapes = [tuple(r.shape) for r in r_outs]
|
| 1057 |
+
# pointwise band: each element judged against its own measured order sensitivity
|
| 1058 |
bands = None
|
| 1059 |
band = 0
|
| 1060 |
band_score = 0.0
|
|
|
|
| 1078 |
max_ulp = 0
|
| 1079 |
mean_ulp = 0.0
|
| 1080 |
first_div = -1
|
| 1081 |
+
first_div_nd = None
|
| 1082 |
n_el = 0
|
| 1083 |
n_above_band = 0
|
| 1084 |
+
n_diverging = 0
|
| 1085 |
+
n_cand_high = 0
|
| 1086 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 1087 |
worst_elements = []
|
| 1088 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
|
|
| 1100 |
mean_ulp += float(d.double().sum().item())
|
| 1101 |
n_el += d.numel()
|
| 1102 |
nz = (d > 0)
|
| 1103 |
+
n_diverging += int(nz.sum().item())
|
| 1104 |
+
n_cand_high += int(((c > r) & nz).sum().item())
|
| 1105 |
if first_div < 0 and nz.any():
|
| 1106 |
first_div = int(nz.flatten().nonzero()[0].item())
|
| 1107 |
+
first_div_nd = _unravel(first_div, r.shape)
|
| 1108 |
if nz.any():
|
| 1109 |
buckets = torch.clamp(
|
| 1110 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
|
|
| 1121 |
if val > 0:
|
| 1122 |
worst_elements.append({
|
| 1123 |
"output": oi, "index": int(ix),
|
| 1124 |
+
"index_nd": _unravel(int(ix), r.shape),
|
| 1125 |
"ref": float(rf[ix].item()),
|
| 1126 |
"cand": float(cf[ix].item()),
|
| 1127 |
"ulp": int(val)})
|
|
|
|
| 1141 |
inf_r += int(torch.isinf(r).sum().item())
|
| 1142 |
inf_c += int(torch.isinf(c).sum().item())
|
| 1143 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
| 1144 |
+
frac_pos = n_cand_high / max(n_diverging, 1)
|
| 1145 |
trial_rep = {
|
| 1146 |
"max_ulp": max_ulp,
|
| 1147 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 1148 |
"first_divergent_index": first_div,
|
| 1149 |
+
"first_divergent_nd": first_div_nd,
|
| 1150 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 1151 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 1152 |
"order_band_ulp": band,
|
| 1153 |
"band_score": round(band_score, 2),
|
| 1154 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
| 1155 |
+
"bias_one_sided_frac": round(max(frac_pos, 1 - frac_pos), 4)
|
| 1156 |
+
if n_diverging else 0.0,
|
| 1157 |
"worst_elements": worst_elements[:worst_k],
|
| 1158 |
}
|
| 1159 |
per_trial.append(trial_rep)
|
| 1160 |
if max_ulp > worst["max_ulp"]:
|
| 1161 |
worst = trial_rep
|
|
|
|
| 1162 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 1163 |
+
banded = order_shuffle is not None or ref_variants
|
| 1164 |
+
if banded:
|
| 1165 |
score = max(t["band_score"] for t in per_trial)
|
| 1166 |
ulp_ok = score <= band_margin
|
| 1167 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
|
|
| 1176 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 1177 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 1178 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 1179 |
+
# one-sided bias gate: a uniform small error is above-band nearly everywhere in one direction
|
| 1180 |
+
bias_fail = False
|
| 1181 |
+
if banded and (ulp_ok or tol_ok):
|
| 1182 |
+
w_frac = max(t["frac_above_band"] for t in per_trial)
|
| 1183 |
+
w_side = max(t["bias_one_sided_frac"] for t in per_trial)
|
| 1184 |
+
if w_frac > frac_above_tol and w_side >= 0.9:
|
| 1185 |
+
bias_fail = True
|
| 1186 |
+
criterion += ("; FAILED bias gate: frac_above_band %.3f > %s "
|
| 1187 |
+
"with one-sided fraction %.2f" %
|
| 1188 |
+
(w_frac, frac_above_tol, w_side))
|
| 1189 |
+
passed = bool((ulp_ok or tol_ok) and not bias_fail)
|
| 1190 |
+
hints = [] if passed else _compare_hints(worst)
|
| 1191 |
+
if passed:
|
| 1192 |
+
verdict = "pass: " + (
|
| 1193 |
+
f"max {max_seen} ULP within the order band" if banded
|
| 1194 |
+
else f"max {max_seen} ULP")
|
| 1195 |
+
elif bias_fail:
|
| 1196 |
+
verdict = ("fail: uniform bias, %.0f%% of elements above their "
|
| 1197 |
+
"order band in one direction" %
|
| 1198 |
+
(100 * max(t["frac_above_band"] for t in per_trial)))
|
| 1199 |
+
else:
|
| 1200 |
+
w0 = worst["worst_elements"][0] if worst["worst_elements"] else None
|
| 1201 |
+
verdict = "fail: max %d ULP" % max_seen + (
|
| 1202 |
+
" at index %s (ref %.6g, cand %.6g)" %
|
| 1203 |
+
(w0["index_nd"], w0["ref"], w0["cand"]) if w0 else "")
|
| 1204 |
+
rep = {
|
| 1205 |
+
"pass": passed,
|
| 1206 |
+
"verdict": verdict,
|
| 1207 |
"criterion": criterion,
|
| 1208 |
"worst_trial": worst,
|
| 1209 |
"max_abs": max_abs_seen,
|
|
|
|
| 1211 |
"trials": per_trial,
|
| 1212 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 1213 |
}
|
| 1214 |
+
if hints:
|
| 1215 |
+
rep["hints"] = hints
|
| 1216 |
+
return rep
|
| 1217 |
|
| 1218 |
|
| 1219 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
|
|
| 1229 |
return False
|
| 1230 |
|
| 1231 |
|
| 1232 |
+
class _RandnMaker:
|
| 1233 |
+
"""Picklable default maker: `arity` same-shape randn tensors (zeros for empty shapes)."""
|
| 1234 |
+
|
| 1235 |
+
def __init__(self, arity=1):
|
| 1236 |
+
self.arity = arity
|
| 1237 |
+
|
| 1238 |
+
def __call__(self, shape, dtype=torch.float32):
|
| 1239 |
+
n = math.prod(shape)
|
| 1240 |
+
if n:
|
| 1241 |
+
out = tuple(torch.randn(tuple(shape), device="cuda",
|
| 1242 |
+
dtype=dtype)
|
| 1243 |
+
for _ in range(self.arity))
|
| 1244 |
+
else:
|
| 1245 |
+
out = tuple(torch.zeros(tuple(shape), device="cuda",
|
| 1246 |
+
dtype=dtype)
|
| 1247 |
+
for _ in range(self.arity))
|
| 1248 |
+
return out if self.arity > 1 else out[0]
|
| 1249 |
+
|
| 1250 |
+
|
| 1251 |
+
def _default_make(arity=1):
|
| 1252 |
+
"""Default maker instance; a class so isolate=True can transport it."""
|
| 1253 |
+
return _RandnMaker(arity)
|
| 1254 |
+
|
| 1255 |
+
|
| 1256 |
def _fuzz_build(make, shape, dtype):
|
| 1257 |
try:
|
| 1258 |
out = make(tuple(shape), dtype)
|
|
|
|
| 1269 |
if v == "strided_rows":
|
| 1270 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 1271 |
if v == "offset_slice":
|
| 1272 |
+
# storage-offset-1 view: misaligned for vectorized loads
|
| 1273 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 1274 |
if v == "overflow_expand":
|
| 1275 |
if x.numel() == 0:
|
|
|
|
| 1283 |
|
| 1284 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 1285 |
stop_on_context_loss=True):
|
| 1286 |
+
"""All variant cases for one (shape, dtype) group; returns (rows, context_lost)."""
|
|
|
|
| 1287 |
rows = []
|
| 1288 |
bases = _fuzz_build(make, shape, dtype)
|
| 1289 |
lost = False
|
|
|
|
| 1320 |
got = op(*xs)
|
| 1321 |
want = ref(*xs)
|
| 1322 |
if v == "overflow_expand":
|
| 1323 |
+
# bounded comparison: head plus the high-index tail where int32 indexing wraps
|
|
|
|
| 1324 |
w = 1 << 20
|
| 1325 |
gf, wf = got.flatten(), want.flatten()
|
| 1326 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
|
|
| 1345 |
return rows, lost
|
| 1346 |
|
| 1347 |
|
| 1348 |
+
def _fn_spec(fn, name):
|
| 1349 |
+
"""Picklable transport spec for a callable, with a source fallback for local defs."""
|
| 1350 |
+
import pickle
|
| 1351 |
+
try:
|
| 1352 |
+
return ("pickle", pickle.dumps(fn), None)
|
| 1353 |
+
except Exception: # noqa: BLE001
|
| 1354 |
+
import inspect
|
| 1355 |
+
import textwrap
|
| 1356 |
+
try:
|
| 1357 |
+
src = textwrap.dedent(inspect.getsource(fn))
|
| 1358 |
+
except (OSError, TypeError):
|
| 1359 |
+
src = ""
|
| 1360 |
+
if not src.startswith("def "):
|
| 1361 |
+
raise ValueError(
|
| 1362 |
+
f"{name} is not picklable and not a plain def; define "
|
| 1363 |
+
"op/ref/make as top-level functions (not lambdas or "
|
| 1364 |
+
"closures) to use isolate=True") from None
|
| 1365 |
+
return ("source", src, fn.__name__)
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
_POOL_BOOTSTRAP = r"""
|
| 1369 |
+
import json, os, pickle, sys
|
| 1370 |
meta = json.load(open(sys.argv[1], "r"))
|
| 1371 |
+
sys.path[:0] = meta["sys_path"]
|
| 1372 |
try:
|
| 1373 |
import kernels
|
| 1374 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
|
|
| 1381 |
import load_local
|
| 1382 |
_mk = load_local.load()
|
| 1383 |
import torch
|
| 1384 |
+
_ns = {"torch": torch}
|
| 1385 |
+
def _resolve(spec):
|
| 1386 |
+
kind, payload, name = spec
|
| 1387 |
+
if kind == "pickle":
|
| 1388 |
+
return pickle.loads(payload)
|
| 1389 |
+
exec(payload, _ns)
|
| 1390 |
+
return _ns[name]
|
| 1391 |
+
_op, _ref, _make = (_resolve(s) for s in
|
| 1392 |
+
pickle.load(open(meta["fns"], "rb")))
|
| 1393 |
+
print("ready", flush=True)
|
| 1394 |
+
for line in sys.stdin:
|
| 1395 |
+
job = json.loads(line)
|
| 1396 |
+
rows, _l = _mk._fuzz_group(
|
| 1397 |
+
_op, _ref, _make, tuple(job["shape"]),
|
| 1398 |
+
getattr(torch, job["dtype"]), tuple(job["variants"]),
|
| 1399 |
+
job["ulp_tol"], job["device"], stop_on_context_loss=False)
|
| 1400 |
+
pickle.dump(rows, open(job["out"], "wb"))
|
| 1401 |
+
print("done", flush=True)
|
| 1402 |
"""
|
| 1403 |
|
| 1404 |
|
| 1405 |
+
class _FuzzPool:
|
| 1406 |
+
"""One persistent isolation worker fed groups over stdin, respawned only after a crash."""
|
| 1407 |
+
|
| 1408 |
+
def __init__(self, meta_path, stderr_path, timeout_s):
|
| 1409 |
+
self.meta_path = meta_path
|
| 1410 |
+
self.stderr_path = stderr_path
|
| 1411 |
+
self.timeout_s = timeout_s
|
| 1412 |
+
self.proc = None
|
| 1413 |
+
self.q = None
|
| 1414 |
+
|
| 1415 |
+
def _spawn(self):
|
| 1416 |
+
import queue
|
| 1417 |
+
import subprocess
|
| 1418 |
+
import sys as _sys
|
| 1419 |
+
import threading
|
| 1420 |
+
self.errf = open(self.stderr_path, "ab")
|
| 1421 |
+
self.proc = subprocess.Popen(
|
| 1422 |
+
[_sys.executable, "-c", _POOL_BOOTSTRAP, self.meta_path],
|
| 1423 |
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
| 1424 |
+
stderr=self.errf, text=True)
|
| 1425 |
+
self.q = queue.Queue()
|
| 1426 |
+
|
| 1427 |
+
def reader(p, q):
|
| 1428 |
+
for line in p.stdout:
|
| 1429 |
+
q.put(line.strip())
|
| 1430 |
+
threading.Thread(target=reader, args=(self.proc, self.q),
|
| 1431 |
+
daemon=True).start()
|
| 1432 |
+
try:
|
| 1433 |
+
first = self.q.get(timeout=self.timeout_s)
|
| 1434 |
+
except Exception: # noqa: BLE001
|
| 1435 |
+
first = None
|
| 1436 |
+
return first == "ready"
|
| 1437 |
+
|
| 1438 |
+
def run(self, job_line):
|
| 1439 |
+
"""Returns True when the worker reports done, False on crash or timeout."""
|
| 1440 |
+
import queue
|
| 1441 |
+
if self.proc is None or self.proc.poll() is not None:
|
| 1442 |
+
if not self._spawn():
|
| 1443 |
+
return False
|
| 1444 |
+
try:
|
| 1445 |
+
self.proc.stdin.write(job_line + "\n")
|
| 1446 |
+
self.proc.stdin.flush()
|
| 1447 |
+
except OSError:
|
| 1448 |
+
return False
|
| 1449 |
+
try:
|
| 1450 |
+
return self.q.get(timeout=self.timeout_s) == "done"
|
| 1451 |
+
except queue.Empty:
|
| 1452 |
+
if self.proc.poll() is None:
|
| 1453 |
+
self.proc.kill()
|
| 1454 |
+
return False
|
| 1455 |
+
|
| 1456 |
+
def stderr_tail(self):
|
| 1457 |
+
try:
|
| 1458 |
+
self.errf.flush()
|
| 1459 |
+
with open(self.stderr_path, "rb") as f:
|
| 1460 |
+
return f.read()[-400:].decode(errors="replace")
|
| 1461 |
+
except OSError:
|
| 1462 |
+
return ""
|
| 1463 |
+
|
| 1464 |
+
def close(self):
|
| 1465 |
+
if self.proc is not None and self.proc.poll() is None:
|
| 1466 |
+
self.proc.kill()
|
| 1467 |
+
try:
|
| 1468 |
+
self.errf.close()
|
| 1469 |
+
except Exception: # noqa: BLE001
|
| 1470 |
+
pass
|
| 1471 |
+
|
| 1472 |
+
|
| 1473 |
+
def _fuzz_hints(rows):
|
| 1474 |
+
hints = []
|
| 1475 |
+
bad = [c for c in rows if c["status"] == "mismatch"]
|
| 1476 |
+
if bad and all(c["variant"] in ("transposed", "strided_rows",
|
| 1477 |
+
"offset_slice") for c in bad):
|
| 1478 |
+
hints.append("mismatches only on non-contiguous variants: the op "
|
| 1479 |
+
"assumes contiguity; call .contiguous() or honor "
|
| 1480 |
+
"strides")
|
| 1481 |
+
if any(c["variant"] == "overflow_expand" and
|
| 1482 |
+
c["status"] == "mismatch" for c in rows):
|
| 1483 |
+
hints.append("overflow_expand mismatch: int32 indexing wraps past "
|
| 1484 |
+
"2^31 elements; use 64-bit index arithmetic")
|
| 1485 |
+
return hints
|
| 1486 |
+
|
| 1487 |
+
|
| 1488 |
+
def fuzz_shapes(op, ref, make=None, shapes=None, variants=_FUZZ_VARIANTS,
|
| 1489 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 1490 |
+
isolate_timeout_s=180, device="cuda", arity=1):
|
| 1491 |
+
"""Edge shapes, dtypes, and layout mutations with per-case repro recipes; isolate=True survives context-killing candidates via a pooled worker."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1492 |
if shapes is None:
|
| 1493 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1494 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
| 1495 |
+
if make is None:
|
| 1496 |
+
make = _default_make(arity)
|
| 1497 |
|
| 1498 |
rows = []
|
| 1499 |
lost = False
|
| 1500 |
if isolate:
|
| 1501 |
import json
|
| 1502 |
import pickle
|
|
|
|
| 1503 |
import sys as _sys
|
| 1504 |
import tempfile
|
| 1505 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1506 |
fns = os.path.join(tmpdir, "fns.pkl")
|
| 1507 |
+
specs = [_fn_spec(f, n) for f, n in ((op, "op"), (ref, "ref"),
|
| 1508 |
+
(make, "make"))]
|
| 1509 |
with open(fns, "wb") as f:
|
| 1510 |
+
pickle.dump(specs, f)
|
| 1511 |
+
meta_path = os.path.join(tmpdir, "meta.json")
|
| 1512 |
+
with open(meta_path, "w", encoding="utf-8") as f:
|
| 1513 |
+
json.dump({"sys_path": [p for p in _sys.path
|
| 1514 |
+
if isinstance(p, str)],
|
| 1515 |
+
"repo": _REPO_ID, "version": _REPO_VERSION,
|
| 1516 |
+
"fns": fns}, f)
|
| 1517 |
+
pool = _FuzzPool(meta_path, os.path.join(tmpdir, "stderr.log"),
|
| 1518 |
+
isolate_timeout_s)
|
| 1519 |
+
try:
|
| 1520 |
+
for dtype in dtypes:
|
| 1521 |
+
for shape in shapes:
|
| 1522 |
+
out = os.path.join(tmpdir, "rows.pkl")
|
| 1523 |
+
if os.path.exists(out):
|
| 1524 |
+
os.remove(out)
|
| 1525 |
+
job = json.dumps({
|
| 1526 |
"shape": list(shape),
|
| 1527 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1528 |
"variants": list(variants),
|
| 1529 |
"ulp_tol": ulp_tol, "device": device,
|
| 1530 |
+
"out": out})
|
| 1531 |
+
ok = pool.run(job)
|
| 1532 |
+
if ok and os.path.exists(out):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
with open(out, "rb") as f:
|
| 1534 |
+
rows.extend(pickle.load(f))
|
| 1535 |
+
else:
|
| 1536 |
+
rows.append({
|
| 1537 |
+
"shape": tuple(shape), "variant": "*",
|
| 1538 |
+
"dtype": str(dtype).replace("torch.", ""),
|
| 1539 |
+
"status": "crashed_isolated",
|
| 1540 |
+
"stderr_tail": pool.stderr_tail()})
|
| 1541 |
+
finally:
|
| 1542 |
+
pool.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1543 |
else:
|
| 1544 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1545 |
for gi, (dtype, shape) in enumerate(pending):
|
|
|
|
| 1556 |
n_bad = sum(1 for r in rows
|
| 1557 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1558 |
"skipped_empty"))
|
| 1559 |
+
rep = {"cases": rows, "failed": n_bad, "total": len(rows),
|
| 1560 |
+
"context_lost": lost}
|
| 1561 |
+
hints = _fuzz_hints(rows)
|
| 1562 |
+
if hints:
|
| 1563 |
+
rep["hints"] = hints
|
| 1564 |
+
if n_bad == 0:
|
| 1565 |
+
rep["verdict"] = "pass: %d/%d cases ok" % (len(rows) - n_bad,
|
| 1566 |
+
len(rows))
|
| 1567 |
+
else:
|
| 1568 |
+
worst_case = next(c for c in rows
|
| 1569 |
+
if c["status"] not in ("ok", "skipped_vram",
|
| 1570 |
+
"skipped_empty"))
|
| 1571 |
+
rep["verdict"] = "fail: %d/%d cases (%s at %s %s)" % (
|
| 1572 |
+
n_bad, len(rows), worst_case["status"], worst_case["shape"],
|
| 1573 |
+
worst_case["variant"])
|
| 1574 |
+
return rep
|
| 1575 |
|
| 1576 |
|
| 1577 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1578 |
check=None, budget_s=None, race=False):
|
| 1579 |
+
"""Launch-configuration search, fastest first, with a correctness gate per row and no silently dropped points."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1580 |
keys = list(grid.keys())
|
| 1581 |
|
| 1582 |
def expand(i, acc):
|
|
|
|
| 1641 |
continue
|
| 1642 |
rows.append(run_point(p, iters))
|
| 1643 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
| 1644 |
+
if rows:
|
| 1645 |
+
n_pruned = sum(1 for r in rows if r.get("status") == "pruned_race")
|
| 1646 |
+
n_skip = sum(1 for r in rows if r.get("status") == "not_run")
|
| 1647 |
+
best = rows[0]
|
| 1648 |
+
rows_verdict = "fastest %s at %.4g ms" % (
|
| 1649 |
+
{k: best[k] for k in keys}, best["median_ms"])
|
| 1650 |
+
if n_pruned:
|
| 1651 |
+
rows_verdict += "; %d pruned" % n_pruned
|
| 1652 |
+
if n_skip:
|
| 1653 |
+
rows_verdict += "; %d not run" % n_skip
|
| 1654 |
+
best["verdict"] = rows_verdict
|
| 1655 |
return rows
|
| 1656 |
|
| 1657 |
|
| 1658 |
+
def judge(cand, ref, inputs=None, gen=None, bytes=None, flops=None,
|
| 1659 |
+
dtype=None, order_shuffle=None, make=None, quick_fuzz=True,
|
| 1660 |
+
device="cuda"):
|
| 1661 |
+
"""One-call verdict: compare, a quick fuzz, and a roofline bench against the cached dossier."""
|
| 1662 |
+
if (gen is None) == (inputs is None):
|
| 1663 |
+
raise ValueError("pass exactly one of inputs (concrete tensors) "
|
| 1664 |
+
"or gen (seed -> args)")
|
| 1665 |
+
if inputs is not None and isinstance(inputs, torch.Tensor):
|
| 1666 |
+
inputs = (inputs,)
|
| 1667 |
+
args0 = tuple(inputs) if inputs is not None else gen(0)
|
| 1668 |
+
arity = len(args0)
|
| 1669 |
+
if dtype is None:
|
| 1670 |
+
dt0 = args0[0].dtype
|
| 1671 |
+
dtype = {torch.float16: "fp16", torch.bfloat16: "bf16"}.get(
|
| 1672 |
+
dt0, "f32")
|
| 1673 |
+
dossier = probe_device(device, quick=True, cached=True,
|
| 1674 |
+
tenancy_check=False)
|
| 1675 |
+
cmp_rep = compare(ref, cand, gen=gen, inputs=inputs, trials=4,
|
| 1676 |
+
order_shuffle=order_shuffle)
|
| 1677 |
+
fuzz_rep = None
|
| 1678 |
+
if quick_fuzz:
|
| 1679 |
+
fuzz_rep = fuzz_shapes(
|
| 1680 |
+
cand, ref, make=make, arity=arity,
|
| 1681 |
+
shapes=[(1, 17), (3, 63), (128, 128)],
|
| 1682 |
+
variants=("contig", "transposed", "offset_slice"),
|
| 1683 |
+
dtypes=(args0[0].dtype,))
|
| 1684 |
+
bench_rep = None
|
| 1685 |
+
if inputs is not None:
|
| 1686 |
+
bench_rep = bench(lambda: cand(*inputs), iters=32, warmup=6,
|
| 1687 |
+
bytes=bytes, flops=flops, dossier=dossier,
|
| 1688 |
+
dtype=dtype, device=device)
|
| 1689 |
+
passed = cmp_rep["pass"] and (fuzz_rep is None or
|
| 1690 |
+
fuzz_rep["failed"] == 0)
|
| 1691 |
+
parts = [cmp_rep["verdict"]]
|
| 1692 |
+
if fuzz_rep is not None:
|
| 1693 |
+
parts.append(fuzz_rep["verdict"])
|
| 1694 |
+
if bench_rep is not None:
|
| 1695 |
+
parts.append(bench_rep["verdict"])
|
| 1696 |
+
rep = {"pass": passed, "verdict" : " | ".join(parts),
|
| 1697 |
+
"compare": cmp_rep, "fuzz": fuzz_rep, "bench": bench_rep,
|
| 1698 |
+
"device": dossier["device"]["name"]}
|
| 1699 |
+
hints = list(cmp_rep.get("hints", []))
|
| 1700 |
+
if fuzz_rep is not None:
|
| 1701 |
+
hints += fuzz_rep.get("hints", [])
|
| 1702 |
+
if hints:
|
| 1703 |
+
rep["hints"] = hints
|
| 1704 |
+
return rep
|
| 1705 |
+
|
| 1706 |
+
|
| 1707 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1708 |
+
"""Device buffer for MK_STAMP (1-D) or MK_STAMP_BLOCK ([blocks, n]) stamps."""
|
|
|
|
|
|
|
| 1709 |
if blocks is not None:
|
| 1710 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1711 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
|
|
| 1715 |
|
| 1716 |
|
| 1717 |
def pinned_dossiers():
|
| 1718 |
+
"""Names of the release-pinned dossiers, one per certified architecture."""
|
|
|
|
| 1719 |
return sorted(_PINNED.keys())
|
| 1720 |
|
| 1721 |
|
|
|
|
| 1734 |
|
| 1735 |
|
| 1736 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1737 |
+
"""Per-leaf relative drift of a fresh dossier against the release pin for the same architecture."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1738 |
if pinned is None:
|
| 1739 |
arch = current["device"]["sm_arch"]
|
| 1740 |
matches = [k for k, v in _PINNED.items()
|
|
|
|
| 1757 |
"rel": round(rel, 4)})
|
| 1758 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1759 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1760 |
+
rep = {"entries": entries, "worst_rel": worst, "over_tol": over,
|
| 1761 |
+
"rel_tol": rel_tol, "compared": len(entries),
|
| 1762 |
+
"pass": len(over) == 0}
|
| 1763 |
+
rep["verdict"] = ("pass: worst drift %.3f over %d entries" %
|
| 1764 |
+
(worst, len(entries))) if rep["pass"] else \
|
| 1765 |
+
("fail: %d entries over %.2f, worst %s at %.3f" %
|
| 1766 |
+
(len(over), rel_tol, over[0]["path"], worst))
|
| 1767 |
+
return rep
|
| 1768 |
|
| 1769 |
|
| 1770 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1771 |
device="cuda"):
|
| 1772 |
+
"""Run the shipped MK_STAMP_BLOCK example (load, compute, drained store) and decode its stamps."""
|
|
|
|
|
|
|
| 1773 |
props = torch.cuda.get_device_properties(device)
|
| 1774 |
if blocks is None:
|
| 1775 |
blocks = props.multi_processor_count
|
|
|
|
| 1782 |
|
| 1783 |
|
| 1784 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1785 |
+
"""Decode MK_STAMP deltas to ticks and microseconds; 2-D buffers yield min/median/max across blocks."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1786 |
if clock_ghz is None:
|
| 1787 |
clock_ghz = measure_clock(stamps.device)
|
| 1788 |
|
|
|
|
| 1818 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1819 |
"blocks_reporting": int(live.sum().item()),
|
| 1820 |
"blocks_total": int(live.numel())}
|
| 1821 |
+
|
| 1822 |
+
|
| 1823 |
+
_SCHEMAS = {
|
| 1824 |
+
"probe_device": {
|
| 1825 |
+
"device": "name, sm_arch, sms, l2_bytes, vram_bytes, torch, "
|
| 1826 |
+
"cuda_runtime, ecc, driver, power_limit_w",
|
| 1827 |
+
"clock": "max/min/final GHz under sustained FMA load plus sag_pct",
|
| 1828 |
+
"dram_bw": "GB/s: triad_4B/8B/16B, read_only, write_only, "
|
| 1829 |
+
"random_gather_16B",
|
| 1830 |
+
"l2_bw": "GB/s over an L2-resident working set",
|
| 1831 |
+
"latency": "cycles: smem, l1, l2, dram (min of 3 chases) plus "
|
| 1832 |
+
"ns_per_cycle",
|
| 1833 |
+
"fma_f32 / fma_f64": "TFLOP/s of the register-resident pipes",
|
| 1834 |
+
"fma_ilp": "TFLOP/s at 1/2/4 chains plus ratio_4_1; near 4 means "
|
| 1835 |
+
"latency-bound",
|
| 1836 |
+
"mma": "TFLOP/s per dtype; sparse carries executed and "
|
| 1837 |
+
"dense_equivalent; verified_k_lanes carries "
|
| 1838 |
+
"executed_k_per_iter, vs_nominal, calibration_linear; "
|
| 1839 |
+
"fp8_accum carries the accumulate-precision stall",
|
| 1840 |
+
"mma_fed": "TFLOP/s of smem-staged fp16 tiles (staging exposed)",
|
| 1841 |
+
"smem_bw": "GB/s aggregate, conflict-light pattern",
|
| 1842 |
+
"smem_conflict_penalty": "time ratio vs stride 1 per lane stride",
|
| 1843 |
+
"transfer_bw": "GB/s: h2d/d2h pinned and pageable, d2d",
|
| 1844 |
+
"atomics_f32": "Gop/s curve over distinct addresses plus "
|
| 1845 |
+
"shared_same_addr",
|
| 1846 |
+
"launch_overhead": "us: torch_queued, torch_round_trip, "
|
| 1847 |
+
"raw_queued, raw_round_trip",
|
| 1848 |
+
"grid_barrier": "us per grid.sync or 'unsupported'",
|
| 1849 |
+
"occupancy_blocks_per_sm": "API table by (threads, dyn smem)",
|
| 1850 |
+
"occupancy_knees_fma_tflops": "timed dependent-chain table",
|
| 1851 |
+
},
|
| 1852 |
+
"bench": {
|
| 1853 |
+
"median_ms": "median over accepted samples",
|
| 1854 |
+
"sustained_ms": "median over all samples",
|
| 1855 |
+
"median_ci95_ms / p05_ms / p95_ms / min_ms / mean_ms":
|
| 1856 |
+
"distribution of accepted samples",
|
| 1857 |
+
"samples / rejected_throttled / rejection_basis":
|
| 1858 |
+
"counts and whether rejection used sm_clock or "
|
| 1859 |
+
"bandwidth_consistency",
|
| 1860 |
+
"no_clean_window": "present when every window ran throttled; the "
|
| 1861 |
+
"median is then a throttled-regime number",
|
| 1862 |
+
"clock_ghz": "best and worst SM clock probes around the run",
|
| 1863 |
+
"achieved_gbs / achieved_tflops / arithmetic_intensity":
|
| 1864 |
+
"rates from declared traffic",
|
| 1865 |
+
"roofline": "bound, ridge_flops_per_byte, pct_of_roof, peak_dtype",
|
| 1866 |
+
"graph_captured / graph_fallback_reason": "CUDA-graph state",
|
| 1867 |
+
"alloc_growth_bytes_per_call": "present when fn leaks allocations",
|
| 1868 |
+
"host_overhead_suspected": "present when fn syncs or does host "
|
| 1869 |
+
"work inside; ops under ~50 us are "
|
| 1870 |
+
"below the detection floor",
|
| 1871 |
+
"verdict": "one-sentence summary",
|
| 1872 |
+
},
|
| 1873 |
+
"compare": {
|
| 1874 |
+
"pass": "final verdict incl. the one-sided bias gate",
|
| 1875 |
+
"verdict / criterion / hints": "summary, acceptance rule, and "
|
| 1876 |
+
"failure-pattern suggestions",
|
| 1877 |
+
"worst_trial": "max_ulp, mean_ulp, first_divergent_index(+_nd), "
|
| 1878 |
+
"NaN/Inf census, order_band_ulp, band_score, "
|
| 1879 |
+
"frac_above_band, bias_one_sided_frac, "
|
| 1880 |
+
"worst_elements (index, index_nd, ref, cand, ulp)",
|
| 1881 |
+
"max_abs / max_rel / ulp_hist_log2": "aggregate evidence",
|
| 1882 |
+
"floor": "the banded route cannot reject errors under "
|
| 1883 |
+
"band_margin*(band+2) ULP at order-stable elements",
|
| 1884 |
+
},
|
| 1885 |
+
"fuzz_shapes": {
|
| 1886 |
+
"cases": "per-case shape, variant, dtype, status, repro recipe",
|
| 1887 |
+
"status values": "ok | mismatch | exception | context_killed | "
|
| 1888 |
+
"context_lost | crashed_isolated | skipped_vram "
|
| 1889 |
+
"| skipped_empty",
|
| 1890 |
+
"failed / total / context_lost / verdict / hints": "aggregates",
|
| 1891 |
+
},
|
| 1892 |
+
"sweep": {
|
| 1893 |
+
"rows": "grid point plus median_ms, rejected_throttled, check?, "
|
| 1894 |
+
"achieved rates, status in pruned_race | not_run",
|
| 1895 |
+
"verdict": "on the fastest row",
|
| 1896 |
+
},
|
| 1897 |
+
"dossier_drift": {
|
| 1898 |
+
"entries": "path, current, pinned, rel per intersecting leaf",
|
| 1899 |
+
"worst_rel / over_tol / compared / pass / verdict": "aggregates",
|
| 1900 |
+
},
|
| 1901 |
+
"judge": {
|
| 1902 |
+
"pass / verdict / hints": "composite over compare, fuzz, bench",
|
| 1903 |
+
"compare / fuzz / bench": "the full sub-reports",
|
| 1904 |
+
},
|
| 1905 |
+
"read_stamps": {
|
| 1906 |
+
"phases": "ticks and us per phase; 2-D input adds min/median/max "
|
| 1907 |
+
"across blocks",
|
| 1908 |
+
"note": "stamps record issue; DRAIN variants include the store "
|
| 1909 |
+
"drain to the coherence point",
|
| 1910 |
+
},
|
| 1911 |
+
}
|
| 1912 |
+
|
| 1913 |
+
|
| 1914 |
+
def schema(name=None):
|
| 1915 |
+
"""Field layout of every report, so an agent can look up what a number means."""
|
| 1916 |
+
if name is None:
|
| 1917 |
+
return _SCHEMAS
|
| 1918 |
+
if name not in _SCHEMAS:
|
| 1919 |
+
raise KeyError(f"no schema for {name!r}; have "
|
| 1920 |
+
f"{sorted(_SCHEMAS)}")
|
| 1921 |
+
return _SCHEMAS[name]
|
build/torch213-cxx11-cu126-x86_64-linux/{_metakernel_cuda_b62a1ce.abi3.so → _metakernel_cuda_0b9de42.abi3.so}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e88c26a134c137ab36fc4580d338ee0a75ac9306710d4681f34b6ac43b6e60c6
|
| 3 |
+
size 900400
|
build/torch213-cxx11-cu126-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _metakernel_cuda_0b9de42
|
| 3 |
+
ops = torch.ops._metakernel_cuda_0b9de42
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_metakernel_cuda_0b9de42::{op_name}"
|
build/torch213-cxx11-cu126-x86_64-linux/_pinned.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
Generated by dev/pin_dossier.py from full probe_device() runs on idle,
|
| 4 |
-
single-tenant cards; consumed by metakernel.dossier_drift.
|
| 5 |
-
"""
|
| 6 |
|
| 7 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 8 |
'confidence': 'high',
|
|
@@ -295,7 +291,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 295 |
'd2h_pinned': 10.8,
|
| 296 |
'h2d_pageable': 10.3,
|
| 297 |
'h2d_pinned': 11.8}}},
|
| 298 |
-
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.
|
| 299 |
'confidence': 'high',
|
| 300 |
'method': 'atomicAdd '
|
| 301 |
'hammer; curve '
|
|
@@ -308,21 +304,21 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 308 |
'contention) to '
|
| 309 |
'one per thread',
|
| 310 |
'units': 'Gop/s',
|
| 311 |
-
'value': {'curve_slots': {'1': 0.
|
| 312 |
-
'145408':
|
| 313 |
-
'4096':
|
| 314 |
-
'512':
|
| 315 |
-
'64': 13.
|
| 316 |
-
'8': 5.
|
| 317 |
-
'shared_same_addr': 12.
|
| 318 |
'clock': {'confidence': 'high',
|
| 319 |
-
'final_ghz': 2.
|
| 320 |
-
'max_ghz': 2.
|
| 321 |
'method': '24 back-to-back FMA '
|
| 322 |
'spin windows of 30M '
|
| 323 |
'ticks; wall/ticks '
|
| 324 |
'per window',
|
| 325 |
-
'min_ghz': 2.
|
| 326 |
'sag_pct': 0.3,
|
| 327 |
'units': 'GHz'},
|
| 328 |
'device': {'cuda_runtime': '12.6',
|
|
@@ -336,7 +332,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 336 |
'sms': 142,
|
| 337 |
'torch': '2.10.0+cu126',
|
| 338 |
'vram_bytes': 48305274880},
|
| 339 |
-
'dram_bw': {'clock_ghz': 2.
|
| 340 |
'confidence': 'high',
|
| 341 |
'method': 'streaming triad '
|
| 342 |
'c=a+s*b per access '
|
|
@@ -356,26 +352,41 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 356 |
'line-efficiency '
|
| 357 |
'claim',
|
| 358 |
'units': 'GB/s',
|
| 359 |
-
'value': {'random_gather_16B': 218.
|
| 360 |
-
'read_only':
|
| 361 |
-
'triad_16B':
|
| 362 |
-
'triad_4B':
|
| 363 |
-
'triad_8B':
|
| 364 |
'write_only': 766.5}},
|
| 365 |
-
'fma_f32': {'clock_ghz': 2.
|
| 366 |
'confidence': 'high',
|
| 367 |
'method': 'register-resident '
|
| 368 |
'FMA chains',
|
| 369 |
'units': 'TFLOP/s',
|
| 370 |
-
'value':
|
| 371 |
-
'fma_f64': {'clock_ghz': 2.
|
| 372 |
'confidence': 'high',
|
| 373 |
'method': 'register-resident '
|
| 374 |
'double FMA chains '
|
| 375 |
'(the DFMA pipe)',
|
| 376 |
'units': 'TFLOP/s',
|
| 377 |
-
'value': 1.
|
| 378 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
'confidence': 'high',
|
| 380 |
'method': 'cooperative '
|
| 381 |
'kernel, 142 '
|
|
@@ -383,8 +394,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 383 |
'threads',
|
| 384 |
'units': 'us per '
|
| 385 |
'grid.sync',
|
| 386 |
-
'value': 0.
|
| 387 |
-
'l2_bw': {'clock_ghz': 2.
|
| 388 |
'confidence': 'medium',
|
| 389 |
'method': 'in-kernel repeated '
|
| 390 |
'triad passes over 72 '
|
|
@@ -394,14 +405,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 394 |
'from HBM; amortized '
|
| 395 |
'over 64 passes',
|
| 396 |
'units': 'GB/s',
|
| 397 |
-
'value':
|
| 398 |
-
'latency': {'clock_ghz': 2.
|
| 399 |
'confidence': 'high',
|
| 400 |
'method': 'Sattolo-cycle '
|
| 401 |
-
'pointer chase
|
| 402 |
-
'
|
| 403 |
-
'
|
| 404 |
-
'ring
|
|
|
|
| 405 |
'self-evicting '
|
| 406 |
'chain (2x L2 in '
|
| 407 |
'touched lines) for '
|
|
@@ -413,40 +425,58 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 413 |
'footprints',
|
| 414 |
'units': 'cycles (single-lane '
|
| 415 |
'dependent loads)',
|
| 416 |
-
'value': {'dram_cycles':
|
| 417 |
'l1_cycles': 45.8,
|
| 418 |
-
'l2_cycles':
|
| 419 |
'ns_per_cycle': 0.372,
|
| 420 |
'smem_cycles': 30.0}},
|
| 421 |
-
'launch_overhead': {'clock_ghz': 2.
|
| 422 |
-
'confidence': '
|
| 423 |
'method': 'empty-kernel '
|
| 424 |
-
'trains
|
| 425 |
-
'
|
| 426 |
-
'launch
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
'units': 'us',
|
| 432 |
-
'value': {'
|
| 433 |
-
'
|
| 434 |
-
|
|
|
|
|
|
|
| 435 |
'confidence': 'medium',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
'method': 'register-fragment '
|
| 437 |
'mma_sync loops (wmma; '
|
| 438 |
'fp8 and 2:4-sparse '
|
| 439 |
'fp16 via inline PTX '
|
| 440 |
-
'm16n8k32;
|
| 441 |
-
'
|
| 442 |
-
'
|
| 443 |
-
'
|
| 444 |
-
'
|
| 445 |
-
'
|
| 446 |
-
'
|
| 447 |
-
'
|
| 448 |
-
'
|
| 449 |
-
'
|
| 450 |
'note': 'dense-tile rate without '
|
| 451 |
'memory traffic; real '
|
| 452 |
'kernels see this only '
|
|
@@ -454,25 +484,54 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 454 |
'behind it',
|
| 455 |
'units': 'TFLOP/s (TOP/s for '
|
| 456 |
'int8)',
|
| 457 |
-
'value': {'bf16':
|
| 458 |
-
'fp16':
|
| 459 |
-
'fp16_sparse_2_4':
|
| 460 |
-
|
| 461 |
-
'
|
| 462 |
-
'
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
| 464 |
'vs_nominal': 1.0},
|
| 465 |
-
'fp16': {'
|
|
|
|
| 466 |
'vs_nominal': 1.0},
|
| 467 |
-
'fp16_sparse_2_4': {'
|
|
|
|
| 468 |
'vs_nominal': 1.0},
|
| 469 |
-
'fp8_e4m3': {'
|
| 470 |
-
'
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
'vs_nominal': 1.0},
|
| 473 |
-
'tf32': {'
|
|
|
|
| 474 |
'vs_nominal': 1.0}}},
|
| 475 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
'confidence': 'high',
|
| 477 |
'method': 'occupancy '
|
| 478 |
'API '
|
|
@@ -501,7 +560,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 501 |
'16KB': 3,
|
| 502 |
'32KB': 3,
|
| 503 |
'48KB': 2}}},
|
| 504 |
-
'occupancy_knees_fma_tflops': {'clock_ghz': 2.
|
| 505 |
'confidence': 'high',
|
| 506 |
'method': 'a '
|
| 507 |
'single '
|
|
@@ -521,17 +580,6 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 521 |
'fixed '
|
| 522 |
'total '
|
| 523 |
'work; '
|
| 524 |
-
'throughput '
|
| 525 |
-
'is '
|
| 526 |
-
'proportional '
|
| 527 |
-
'to '
|
| 528 |
-
'resident '
|
| 529 |
-
'threads '
|
| 530 |
-
'until '
|
| 531 |
-
'the '
|
| 532 |
-
'pipe '
|
| 533 |
-
'saturates, '
|
| 534 |
-
'so '
|
| 535 |
'the '
|
| 536 |
'cliff '
|
| 537 |
'marks '
|
|
@@ -549,15 +597,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 549 |
'above',
|
| 550 |
'units': 'TFLOP/s',
|
| 551 |
'value': {'t1024': {'0KB': 75.5,
|
| 552 |
-
'32KB': 73.
|
| 553 |
-
'48KB':
|
| 554 |
-
't128': {'0KB': 79.
|
| 555 |
'32KB': 60.31,
|
| 556 |
-
'48KB':
|
| 557 |
-
't256': {'0KB':
|
| 558 |
-
'32KB':
|
| 559 |
-
'48KB':
|
| 560 |
-
'smem_bw': {'clock_ghz': 2.
|
| 561 |
'confidence': 'medium',
|
| 562 |
'method': 'block-resident '
|
| 563 |
'read+write '
|
|
@@ -566,12 +614,56 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 566 |
'aggregated across '
|
| 567 |
'SMs',
|
| 568 |
'note': 'bank-conflict-light '
|
| 569 |
-
'access pattern;
|
| 570 |
-
'
|
| 571 |
-
'
|
|
|
|
| 572 |
'units': 'GB/s',
|
| 573 |
-
'value':
|
| 574 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
'confidence': 'high',
|
| 576 |
'method': '256 MB torch '
|
| 577 |
'copies, '
|
|
@@ -583,8 +675,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 583 |
'direction of '
|
| 584 |
'traffic',
|
| 585 |
'units': 'GB/s',
|
| 586 |
-
'value': {'d2d':
|
| 587 |
-
'd2h_pageable':
|
| 588 |
-
'd2h_pinned': 26.
|
| 589 |
-
'h2d_pageable':
|
| 590 |
'h2d_pinned': 24.0}}}}
|
|
|
|
| 1 |
+
'''Release-pinned device dossiers, one per certified architecture; generated by dev/pin_dossier.py from idle single-tenant runs, consumed by dossier_drift.'''
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 4 |
'confidence': 'high',
|
|
|
|
| 291 |
'd2h_pinned': 10.8,
|
| 292 |
'h2d_pageable': 10.3,
|
| 293 |
'h2d_pinned': 11.8}}},
|
| 294 |
+
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.705,
|
| 295 |
'confidence': 'high',
|
| 296 |
'method': 'atomicAdd '
|
| 297 |
'hammer; curve '
|
|
|
|
| 304 |
'contention) to '
|
| 305 |
'one per thread',
|
| 306 |
'units': 'Gop/s',
|
| 307 |
+
'value': {'curve_slots': {'1': 0.71,
|
| 308 |
+
'145408': 413.99,
|
| 309 |
+
'4096': 184.9,
|
| 310 |
+
'512': 49.84,
|
| 311 |
+
'64': 13.25,
|
| 312 |
+
'8': 5.74},
|
| 313 |
+
'shared_same_addr': 12.23}},
|
| 314 |
'clock': {'confidence': 'high',
|
| 315 |
+
'final_ghz': 2.698,
|
| 316 |
+
'max_ghz': 2.705,
|
| 317 |
'method': '24 back-to-back FMA '
|
| 318 |
'spin windows of 30M '
|
| 319 |
'ticks; wall/ticks '
|
| 320 |
'per window',
|
| 321 |
+
'min_ghz': 2.5,
|
| 322 |
'sag_pct': 0.3,
|
| 323 |
'units': 'GHz'},
|
| 324 |
'device': {'cuda_runtime': '12.6',
|
|
|
|
| 332 |
'sms': 142,
|
| 333 |
'torch': '2.10.0+cu126',
|
| 334 |
'vram_bytes': 48305274880},
|
| 335 |
+
'dram_bw': {'clock_ghz': 2.705,
|
| 336 |
'confidence': 'high',
|
| 337 |
'method': 'streaming triad '
|
| 338 |
'c=a+s*b per access '
|
|
|
|
| 352 |
'line-efficiency '
|
| 353 |
'claim',
|
| 354 |
'units': 'GB/s',
|
| 355 |
+
'value': {'random_gather_16B': 218.1,
|
| 356 |
+
'read_only': 766.5,
|
| 357 |
+
'triad_16B': 682.1,
|
| 358 |
+
'triad_4B': 654.8,
|
| 359 |
+
'triad_8B': 670.9,
|
| 360 |
'write_only': 766.5}},
|
| 361 |
+
'fma_f32': {'clock_ghz': 2.705,
|
| 362 |
'confidence': 'high',
|
| 363 |
'method': 'register-resident '
|
| 364 |
'FMA chains',
|
| 365 |
'units': 'TFLOP/s',
|
| 366 |
+
'value': 86.58},
|
| 367 |
+
'fma_f64': {'clock_ghz': 2.705,
|
| 368 |
'confidence': 'high',
|
| 369 |
'method': 'register-resident '
|
| 370 |
'double FMA chains '
|
| 371 |
'(the DFMA pipe)',
|
| 372 |
'units': 'TFLOP/s',
|
| 373 |
+
'value': 1.257},
|
| 374 |
+
'fma_ilp': {'clock_ghz': 2.705,
|
| 375 |
+
'confidence': 'high',
|
| 376 |
+
'method': '1/2/4 independent '
|
| 377 |
+
'dependent FMA '
|
| 378 |
+
'chains per thread '
|
| 379 |
+
'at one block of 64 '
|
| 380 |
+
'threads per SM; a '
|
| 381 |
+
'ratio near 4 means '
|
| 382 |
+
'latency-bound at '
|
| 383 |
+
'that occupancy',
|
| 384 |
+
'units': 'TFLOP/s',
|
| 385 |
+
'value': {'ilp1': 11.75,
|
| 386 |
+
'ilp2': 23.37,
|
| 387 |
+
'ilp4': 43.03,
|
| 388 |
+
'ratio_4_1': 3.66}},
|
| 389 |
+
'grid_barrier': {'clock_ghz': 2.705,
|
| 390 |
'confidence': 'high',
|
| 391 |
'method': 'cooperative '
|
| 392 |
'kernel, 142 '
|
|
|
|
| 394 |
'threads',
|
| 395 |
'units': 'us per '
|
| 396 |
'grid.sync',
|
| 397 |
+
'value': 0.55},
|
| 398 |
+
'l2_bw': {'clock_ghz': 2.705,
|
| 399 |
'confidence': 'medium',
|
| 400 |
'method': 'in-kernel repeated '
|
| 401 |
'triad passes over 72 '
|
|
|
|
| 405 |
'from HBM; amortized '
|
| 406 |
'over 64 passes',
|
| 407 |
'units': 'GB/s',
|
| 408 |
+
'value': 9590.6},
|
| 409 |
+
'latency': {'clock_ghz': 2.691,
|
| 410 |
'confidence': 'high',
|
| 411 |
'method': 'Sattolo-cycle '
|
| 412 |
+
'pointer chase, min '
|
| 413 |
+
'of 3 runs; 16 KB '
|
| 414 |
+
'ring for L1, 2 MB '
|
| 415 |
+
'TLB-resident ring '
|
| 416 |
+
'for L2, '
|
| 417 |
'self-evicting '
|
| 418 |
'chain (2x L2 in '
|
| 419 |
'touched lines) for '
|
|
|
|
| 425 |
'footprints',
|
| 426 |
'units': 'cycles (single-lane '
|
| 427 |
'dependent loads)',
|
| 428 |
+
'value': {'dram_cycles': 656.9,
|
| 429 |
'l1_cycles': 45.8,
|
| 430 |
+
'l2_cycles': 292.0,
|
| 431 |
'ns_per_cycle': 0.372,
|
| 432 |
'smem_cycles': 30.0}},
|
| 433 |
+
'launch_overhead': {'clock_ghz': 2.705,
|
| 434 |
+
'confidence': 'medium',
|
| 435 |
'method': 'empty-kernel '
|
| 436 |
+
'trains; '
|
| 437 |
+
'torch path '
|
| 438 |
+
'per-launch '
|
| 439 |
+
'through op '
|
| 440 |
+
'dispatch, '
|
| 441 |
+
'raw path '
|
| 442 |
+
'looped '
|
| 443 |
+
'inside one '
|
| 444 |
+
'binding '
|
| 445 |
+
'call',
|
| 446 |
+
'note': 'host-state-dependent; '
|
| 447 |
+
'the '
|
| 448 |
+
'torch-minus-raw '
|
| 449 |
+
'gap is the '
|
| 450 |
+
'dispatch '
|
| 451 |
+
'cost a '
|
| 452 |
+
'torch-launched '
|
| 453 |
+
'candidate '
|
| 454 |
+
'pays',
|
| 455 |
'units': 'us',
|
| 456 |
+
'value': {'raw_queued_us': 6.75,
|
| 457 |
+
'raw_round_trip_us': 13.87,
|
| 458 |
+
'torch_queued_us': 7.09,
|
| 459 |
+
'torch_round_trip_us': 18.53}},
|
| 460 |
+
'mma': {'clock_ghz': 2.705,
|
| 461 |
'confidence': 'medium',
|
| 462 |
+
'fp8_accum': {'fp8_e4m3': {'effective_accum_mantissa_bits': 8,
|
| 463 |
+
'stall_element_value': 16384.0},
|
| 464 |
+
'fp8_e5m2': {'effective_accum_mantissa_bits': 8,
|
| 465 |
+
'stall_element_value': 16384.0}},
|
| 466 |
'method': 'register-fragment '
|
| 467 |
'mma_sync loops (wmma; '
|
| 468 |
'fp8 and 2:4-sparse '
|
| 469 |
'fp16 via inline PTX '
|
| 470 |
+
'm16n8k32); flops '
|
| 471 |
+
'derive from a '
|
| 472 |
+
'sub-saturation '
|
| 473 |
+
'accumulator read-back '
|
| 474 |
+
'with every fragment '
|
| 475 |
+
'element observed, '
|
| 476 |
+
'never the declared '
|
| 477 |
+
'loop shape; sparse '
|
| 478 |
+
'reports executed and '
|
| 479 |
+
'dense-equivalent flops',
|
| 480 |
'note': 'dense-tile rate without '
|
| 481 |
'memory traffic; real '
|
| 482 |
'kernels see this only '
|
|
|
|
| 484 |
'behind it',
|
| 485 |
'units': 'TFLOP/s (TOP/s for '
|
| 486 |
'int8)',
|
| 487 |
+
'value': {'bf16': 369.6,
|
| 488 |
+
'fp16': 365.3,
|
| 489 |
+
'fp16_sparse_2_4': {'dense_equivalent': 711.1,
|
| 490 |
+
'executed': 355.6},
|
| 491 |
+
'fp8_e4m3': 772.3,
|
| 492 |
+
'fp8_e5m2': 771.2,
|
| 493 |
+
'int8': 750.0,
|
| 494 |
+
'tf32': 178.3},
|
| 495 |
+
'verified_k_lanes': {'bf16': {'calibration_linear': True,
|
| 496 |
+
'executed_k_per_iter': 64.0,
|
| 497 |
'vs_nominal': 1.0},
|
| 498 |
+
'fp16': {'calibration_linear': True,
|
| 499 |
+
'executed_k_per_iter': 64.0,
|
| 500 |
'vs_nominal': 1.0},
|
| 501 |
+
'fp16_sparse_2_4': {'calibration_linear': True,
|
| 502 |
+
'executed_k_per_iter': 128.0,
|
| 503 |
'vs_nominal': 1.0},
|
| 504 |
+
'fp8_e4m3': {'calibration_linear': True,
|
| 505 |
+
'executed_k_per_iter': 256.0,
|
| 506 |
+
'vs_nominal': 1.0},
|
| 507 |
+
'fp8_e5m2': {'calibration_linear': True,
|
| 508 |
+
'executed_k_per_iter': 256.0,
|
| 509 |
+
'vs_nominal': 1.0},
|
| 510 |
+
'int8': {'calibration_linear': True,
|
| 511 |
+
'executed_k_per_iter': 64.0,
|
| 512 |
'vs_nominal': 1.0},
|
| 513 |
+
'tf32': {'calibration_linear': True,
|
| 514 |
+
'executed_k_per_iter': 32.0,
|
| 515 |
'vs_nominal': 1.0}}},
|
| 516 |
+
'mma_fed': {'clock_ghz': 2.705,
|
| 517 |
+
'confidence': 'high',
|
| 518 |
+
'method': 'm16n16k16 fp16 '
|
| 519 |
+
'tiles fed from an '
|
| 520 |
+
'L2-resident buffer '
|
| 521 |
+
'through cp.async '
|
| 522 |
+
'double-buffered '
|
| 523 |
+
'shared memory; '
|
| 524 |
+
'accumulator-verified',
|
| 525 |
+
'note': 'the achieved '
|
| 526 |
+
'counterpart to the '
|
| 527 |
+
'register peak: '
|
| 528 |
+
'staging exposed, one '
|
| 529 |
+
'dependent '
|
| 530 |
+
'accumulator chain '
|
| 531 |
+
'per warp',
|
| 532 |
+
'units': 'TFLOP/s',
|
| 533 |
+
'value': 85.2},
|
| 534 |
+
'occupancy_blocks_per_sm': {'clock_ghz': 2.705,
|
| 535 |
'confidence': 'high',
|
| 536 |
'method': 'occupancy '
|
| 537 |
'API '
|
|
|
|
| 560 |
'16KB': 3,
|
| 561 |
'32KB': 3,
|
| 562 |
'48KB': 2}}},
|
| 563 |
+
'occupancy_knees_fma_tflops': {'clock_ghz': 2.705,
|
| 564 |
'confidence': 'high',
|
| 565 |
'method': 'a '
|
| 566 |
'single '
|
|
|
|
| 580 |
'fixed '
|
| 581 |
'total '
|
| 582 |
'work; '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
'the '
|
| 584 |
'cliff '
|
| 585 |
'marks '
|
|
|
|
| 597 |
'above',
|
| 598 |
'units': 'TFLOP/s',
|
| 599 |
'value': {'t1024': {'0KB': 75.5,
|
| 600 |
+
'32KB': 73.71,
|
| 601 |
+
'48KB': 72.69},
|
| 602 |
+
't128': {'0KB': 79.72,
|
| 603 |
'32KB': 60.31,
|
| 604 |
+
'48KB': 45.81},
|
| 605 |
+
't256': {'0KB': 78.79,
|
| 606 |
+
'32KB': 78.85,
|
| 607 |
+
'48KB': 72.97}}},
|
| 608 |
+
'smem_bw': {'clock_ghz': 2.705,
|
| 609 |
'confidence': 'medium',
|
| 610 |
'method': 'block-resident '
|
| 611 |
'read+write '
|
|
|
|
| 614 |
'aggregated across '
|
| 615 |
'SMs',
|
| 616 |
'note': 'bank-conflict-light '
|
| 617 |
+
'access pattern; the '
|
| 618 |
+
'stride sweep below '
|
| 619 |
+
'prices the '
|
| 620 |
+
'conflicted case',
|
| 621 |
'units': 'GB/s',
|
| 622 |
+
'value': 22244.0},
|
| 623 |
+
'smem_conflict_penalty': {'clock_ghz': 2.705,
|
| 624 |
+
'confidence': 'high',
|
| 625 |
+
'method': 'dependent '
|
| 626 |
+
'read-FMA-write '
|
| 627 |
+
'per '
|
| 628 |
+
'thread '
|
| 629 |
+
'at '
|
| 630 |
+
'warp-lane '
|
| 631 |
+
'bank '
|
| 632 |
+
'distance '
|
| 633 |
+
'`stride`; '
|
| 634 |
+
'stride '
|
| 635 |
+
'32 '
|
| 636 |
+
'puts '
|
| 637 |
+
'every '
|
| 638 |
+
'lane '
|
| 639 |
+
'in '
|
| 640 |
+
'one '
|
| 641 |
+
'bank',
|
| 642 |
+
'note': 'the '
|
| 643 |
+
'direct '
|
| 644 |
+
'conflict '
|
| 645 |
+
'measurement '
|
| 646 |
+
'that '
|
| 647 |
+
'disambiguates '
|
| 648 |
+
'a '
|
| 649 |
+
'bank-conflict '
|
| 650 |
+
'signature '
|
| 651 |
+
'from '
|
| 652 |
+
'an ILP '
|
| 653 |
+
'stall',
|
| 654 |
+
'units': 'x '
|
| 655 |
+
'(time '
|
| 656 |
+
'ratio '
|
| 657 |
+
'vs '
|
| 658 |
+
'stride '
|
| 659 |
+
'1)',
|
| 660 |
+
'value': {'stride_1': 1.0,
|
| 661 |
+
'stride_16': 7.13,
|
| 662 |
+
'stride_2': 1.07,
|
| 663 |
+
'stride_32': 14.08,
|
| 664 |
+
'stride_4': 2.04,
|
| 665 |
+
'stride_8': 3.91}},
|
| 666 |
+
'transfer_bw': {'clock_ghz': 2.705,
|
| 667 |
'confidence': 'high',
|
| 668 |
'method': '256 MB torch '
|
| 669 |
'copies, '
|
|
|
|
| 675 |
'direction of '
|
| 676 |
'traffic',
|
| 677 |
'units': 'GB/s',
|
| 678 |
+
'value': {'d2d': 324.0,
|
| 679 |
+
'd2h_pageable': 7.8,
|
| 680 |
+
'd2h_pinned': 26.3,
|
| 681 |
+
'h2d_pageable': 7.6,
|
| 682 |
'h2d_pinned': 24.0}}}}
|
build/torch213-cxx11-cu126-x86_64-linux/metadata.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 7 |
"backend": {
|
| 8 |
"type": "cuda",
|
| 9 |
"archs": [
|
|
@@ -16,21 +17,20 @@
|
|
| 16 |
"digest": {
|
| 17 |
"algorithm": "sha256",
|
| 18 |
"files": {
|
| 19 |
-
"__init__.py": "
|
| 20 |
-
"
|
| 21 |
-
"_ops.py": "
|
| 22 |
-
"_pinned.py": "
|
| 23 |
-
"metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 24 |
}
|
| 25 |
},
|
| 26 |
"provenance": {
|
| 27 |
"kernel-builder": {
|
| 28 |
"version": "0.17.0-dev0",
|
| 29 |
-
"sha": "
|
| 30 |
"dirty": false
|
| 31 |
},
|
| 32 |
"kernel": {
|
| 33 |
-
"sha": "
|
| 34 |
"dirty": false
|
| 35 |
}
|
| 36 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
+
"id": "_metakernel_cuda_0b9de42",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
| 7 |
+
"kernel-depends": [],
|
| 8 |
"backend": {
|
| 9 |
"type": "cuda",
|
| 10 |
"archs": [
|
|
|
|
| 17 |
"digest": {
|
| 18 |
"algorithm": "sha256",
|
| 19 |
"files": {
|
| 20 |
+
"__init__.py": "lDQ+fJ0JnI3sQGR3UMnDovBbG4ciYBsaFjmgYdKDTKI=",
|
| 21 |
+
"_metakernel_cuda_0b9de42.abi3.so": "6IwmoTTBN6s2/EWA0zjuCnWskwZxDUaB80tqxDtuYMY=",
|
| 22 |
+
"_ops.py": "x88Bkm1nxRli0nIu2u4ztaJopS1IAZWdN9LZPfspJdo=",
|
| 23 |
+
"_pinned.py": "UyzMniAjfVMIrIbbrSq92KKo5dX5l/GwV3pMVA+eNtM="
|
|
|
|
| 24 |
}
|
| 25 |
},
|
| 26 |
"provenance": {
|
| 27 |
"kernel-builder": {
|
| 28 |
"version": "0.17.0-dev0",
|
| 29 |
+
"sha": "2c40e10d3da1392245ee81bd05b55fb3103b2b34",
|
| 30 |
"dirty": false
|
| 31 |
},
|
| 32 |
"kernel": {
|
| 33 |
+
"sha": "0b9de42fee8a1e52593f2f1f58460c0acc93d7e8",
|
| 34 |
"dirty": false
|
| 35 |
}
|
| 36 |
}
|
build/torch213-cxx11-cu126-x86_64-linux/metakernel/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 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
CHANGED
|
@@ -1,20 +1,4 @@
|
|
| 1 |
-
"""Measuring instrument for the kernel-design loop: timing, adversarial
|
| 2 |
-
correctness comparison, and measured device facts, as loadable ops.
|
| 3 |
-
|
| 4 |
-
from kernels import get_kernel
|
| 5 |
-
mk = get_kernel("phanerozoic/metakernel", version=1,
|
| 6 |
-
trust_remote_code=True)
|
| 7 |
-
|
| 8 |
-
dossier = mk.probe_device() # measured facts
|
| 9 |
-
report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
|
| 10 |
-
verdict = mk.compare(ref, cand, gen) # ULP evidence
|
| 11 |
-
table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
|
| 12 |
-
|
| 13 |
-
Every op returns plain dicts and tensors. Every dossier entry is
|
| 14 |
-
measured by a dedicated probe kernel, carries its SM clock, and labels
|
| 15 |
-
throttled samples; no hardware counters, so entries carry a confidence
|
| 16 |
-
field.
|
| 17 |
-
"""
|
| 18 |
import math
|
| 19 |
import os
|
| 20 |
import time
|
|
@@ -24,9 +8,7 @@ import torch
|
|
| 24 |
try:
|
| 25 |
from ._ops import ops
|
| 26 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 27 |
-
#
|
| 28 |
-
# checkout there is no generated _ops module, so rebuild the op table
|
| 29 |
-
# from the (cached) JIT extension that load_local advertised.
|
| 30 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 31 |
if not _root:
|
| 32 |
raise
|
|
@@ -48,24 +30,25 @@ try:
|
|
| 48 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 49 |
_PINNED = {}
|
| 50 |
|
| 51 |
-
__all__ = ["probe_device", "bench", "compare", "
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"pinned_dossiers", "
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
#
|
| 62 |
_MMA_KINDS = {
|
| 63 |
-
"fp16": (0, 16 * 16, 16,
|
| 64 |
-
"bf16": (1, 16 * 16, 16,
|
| 65 |
-
"tf32": (2, 16 * 16, 8,
|
| 66 |
-
"int8": (3, 16 * 16, 16,
|
| 67 |
-
"fp8_e4m3": (4, 16 * 8, 32,
|
| 68 |
-
"fp16_sparse_2_4": (5, 16 * 8, 32,
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
|
|
@@ -74,7 +57,7 @@ def _sync():
|
|
| 74 |
|
| 75 |
|
| 76 |
def _events(fn, reps):
|
| 77 |
-
"""Time reps calls of fn with one
|
| 78 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 79 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 80 |
for s, e in zip(starts, stops):
|
|
@@ -86,8 +69,7 @@ def _events(fn, reps):
|
|
| 86 |
|
| 87 |
|
| 88 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 89 |
-
"""Effective SM clock in GHz
|
| 90 |
-
Wall time over elapsed ticks is the frequency the ticks ran at."""
|
| 91 |
props = torch.cuda.get_device_properties(device)
|
| 92 |
if blocks is None:
|
| 93 |
blocks = props.multi_processor_count
|
|
@@ -120,8 +102,7 @@ def _entry(value, units, method, clock_ghz, confidence, note=""):
|
|
| 120 |
|
| 121 |
|
| 122 |
def _sattolo_ring(n, device, generator=None):
|
| 123 |
-
"""
|
| 124 |
-
a pointer chase visits every slot before repeating."""
|
| 125 |
perm = torch.randperm(n, generator=generator)
|
| 126 |
ring = torch.empty(n, dtype=torch.int32)
|
| 127 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
@@ -129,8 +110,7 @@ def _sattolo_ring(n, device, generator=None):
|
|
| 129 |
|
| 130 |
|
| 131 |
def _smi_settings(index):
|
| 132 |
-
"""ECC mode, driver,
|
| 133 |
-
when the tool is unavailable."""
|
| 134 |
import subprocess
|
| 135 |
try:
|
| 136 |
out = subprocess.run(
|
|
@@ -150,10 +130,19 @@ def _smi_settings(index):
|
|
| 150 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 151 |
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def mma_tile_check(device="cuda", trials=8):
|
| 154 |
-
"""Random small-integer m16n16k16 tiles through the wmma
|
| 155 |
-
path, compared exactly to the reference GEMM; pins M, N, K, and
|
| 156 |
-
operand mapping (integer operands make fp32 accumulation exact)."""
|
| 157 |
worst = 0.0
|
| 158 |
for t in range(trials):
|
| 159 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
@@ -171,10 +160,83 @@ def mma_tile_check(device="cuda", trials=8):
|
|
| 171 |
"trials": trials}
|
| 172 |
|
| 173 |
|
| 174 |
-
def
|
| 175 |
-
"""
|
| 176 |
-
|
| 177 |
-
a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
props = torch.cuda.get_device_properties(device)
|
| 179 |
sm = props.multi_processor_count
|
| 180 |
l2 = props.L2_cache_size
|
|
@@ -187,8 +249,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 187 |
"vram_bytes": props.total_memory,
|
| 188 |
"torch": torch.__version__,
|
| 189 |
"cuda_runtime": torch.version.cuda,
|
| 190 |
-
# instrument settings
|
| 191 |
-
# dossier records them (None when nvidia-smi is unavailable)
|
| 192 |
**_smi_settings(torch.device(device).index or 0),
|
| 193 |
}
|
| 194 |
}
|
|
@@ -213,9 +274,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 213 |
}
|
| 214 |
clk = max(traj)
|
| 215 |
|
| 216 |
-
# ---- device-memory bandwidth
|
| 217 |
-
|
| 218 |
-
n4 = 16 << 20 # 16M float4 = 256 MB per buffer
|
| 219 |
if props.total_memory < 4 << 30:
|
| 220 |
n4 = 4 << 20
|
| 221 |
a = torch.rand(n4 * 4, device=device)
|
|
@@ -239,7 +299,6 @@ def probe_device(device="cuda", quick=False):
|
|
| 239 |
dtype=torch.int32).to(device)
|
| 240 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 241 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
| 242 |
-
# traffic accounting: idx reads + 16B random loads + streamed stores
|
| 243 |
hbm["random_gather_16B"] = round(
|
| 244 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 245 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
@@ -250,9 +309,9 @@ def probe_device(device="cuda", quick=False):
|
|
| 250 |
"so its nominal number is the achievable rate for "
|
| 251 |
"random access, not a line-efficiency claim")
|
| 252 |
|
| 253 |
-
# ---- L2 bandwidth
|
| 254 |
ws = max(l2 // 4, 1 << 20)
|
| 255 |
-
nl2 = ws // 16
|
| 256 |
al = a[:nl2 * 4].contiguous()
|
| 257 |
bl = b[:nl2 * 4].contiguous()
|
| 258 |
cl = c[:nl2 * 4].contiguous()
|
|
@@ -267,62 +326,61 @@ def probe_device(device="cuda", quick=False):
|
|
| 267 |
"medium", "first pass streams from HBM; amortized "
|
| 268 |
f"over {passes} passes")
|
| 269 |
|
| 270 |
-
# ---- latencies:
|
| 271 |
-
# L1, fits L2 everywhere, and stays TLB-resident after a warm walk;
|
| 272 |
-
# the DRAM chain touches 2x L2 in lines, so it evicts itself.
|
| 273 |
lat = {}
|
| 274 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
| 275 |
-
ring_s = _sattolo_ring(8192, device)
|
| 276 |
-
ops.mk_chase_shared(ring_s, 1 << 14, out64)
|
| 277 |
-
_sync()
|
| 278 |
-
hops = 1 << 16
|
| 279 |
-
ops.mk_chase_shared(ring_s, hops, out64)
|
| 280 |
-
_sync()
|
| 281 |
-
lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
_sync()
|
| 299 |
-
lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 302 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 303 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
_sync()
|
| 308 |
-
lat["dram_cycles"] = round(int(out64[0].item()) / hops_h, 1)
|
| 309 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 310 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 311 |
-
"Sattolo-cycle pointer chase
|
| 312 |
-
"2 MB TLB-resident ring for L2,
|
| 313 |
-
"chain (2x L2 in touched lines)
|
| 314 |
-
idle_clk, "high",
|
| 315 |
"smem includes index arithmetic; HBM includes "
|
| 316 |
"residual TLB effects at large footprints")
|
| 317 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 318 |
|
| 319 |
-
# ---- fp32 FMA and tensor-core throughput
|
| 320 |
blocks = sm * 4
|
| 321 |
iters = 20_000 if quick else 60_000
|
| 322 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 323 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 324 |
reps))
|
| 325 |
-
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 326 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 327 |
"register-resident FMA chains", clk, "high")
|
| 328 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
@@ -334,43 +392,88 @@ def probe_device(device="cuda", quick=False):
|
|
| 334 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 335 |
"register-resident double FMA chains (the DFMA "
|
| 336 |
"pipe)", clk, "high")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
mma = {}
|
| 338 |
mma_klanes = {}
|
| 339 |
-
|
|
|
|
| 340 |
it = 30_000 if quick else 100_000
|
| 341 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 342 |
if r != 0:
|
| 343 |
mma[name] = "unsupported"
|
| 344 |
continue
|
| 345 |
_sync()
|
| 346 |
-
|
| 347 |
-
# the executed K-lane count (exact at these magnitudes)
|
| 348 |
-
cal_iters = 512
|
| 349 |
-
sink[1] = 0.0
|
| 350 |
-
ops.mk_mma(kind, 1, 1, cal_iters, sink)
|
| 351 |
-
_sync()
|
| 352 |
-
gain_per_iter = float(sink[1].item()) / cal_iters
|
| 353 |
mma_klanes[name] = {
|
| 354 |
-
"executed_k_per_iter": round(
|
| 355 |
-
"vs_nominal": round(
|
|
|
|
| 356 |
}
|
|
|
|
|
|
|
| 357 |
ms = _median(_events(
|
| 358 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 362 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 363 |
-
"2:4-sparse fp16 via inline PTX m16n8k32;
|
| 364 |
-
"
|
| 365 |
-
"flops derive from the mean accumulator read-back "
|
| 366 |
"with every fragment element observed, never the "
|
| 367 |
-
"declared loop shape"
|
|
|
|
| 368 |
clk, "medium",
|
| 369 |
"dense-tile rate without memory traffic; real "
|
| 370 |
"kernels see this only when staging hides behind it")
|
| 371 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
# ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
it_sm = 30_000 if quick else 100_000
|
| 375 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 376 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
@@ -380,8 +483,23 @@ def probe_device(device="cuda", quick=False):
|
|
| 380 |
"block-resident read+write streaming (8 B per "
|
| 381 |
"thread-iteration), aggregated across SMs", clk,
|
| 382 |
"medium",
|
| 383 |
-
"bank-conflict-light access pattern;
|
| 384 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
# ---- PCIe / host transfer bandwidth
|
| 387 |
nbytes = 256 << 20
|
|
@@ -412,9 +530,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 412 |
"d2d counts one direction of traffic")
|
| 413 |
del host_pin, host_pag, dev_t, dev_u
|
| 414 |
|
| 415 |
-
# ---- atomic contention curve
|
| 416 |
-
# addresses the whole grid hammers (slots = 1 is full contention;
|
| 417 |
-
# slots = threads is contention-free), plus the shared-memory point
|
| 418 |
n_thr = blocks * 256
|
| 419 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 420 |
at = {"curve_slots": {}}
|
|
@@ -424,7 +540,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 424 |
buf.zero_()
|
| 425 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 426 |
ms = _median(_events(
|
| 427 |
-
lambda
|
| 428 |
reps))
|
| 429 |
at["curve_slots"][str(slots)] = round(
|
| 430 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
@@ -437,7 +553,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 437 |
"of distinct global addresses from 1 (full "
|
| 438 |
"contention) to one per thread", clk, "high")
|
| 439 |
|
| 440 |
-
# ---- launch overhead
|
| 441 |
anchor = sink
|
| 442 |
n_launch = 500 if quick else 2000
|
| 443 |
_sync()
|
|
@@ -452,10 +568,19 @@ def probe_device(device="cuda", quick=False):
|
|
| 452 |
ops.mk_empty(anchor)
|
| 453 |
_sync()
|
| 454 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
|
|
|
|
|
|
| 455 |
d["launch_overhead"] = _entry(
|
| 456 |
-
{"
|
| 457 |
-
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
# ---- grid barrier
|
| 461 |
bar_iters = 500 if quick else 2000
|
|
@@ -471,9 +596,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 471 |
"unavailable or grid too large", clk,
|
| 472 |
"high")
|
| 473 |
|
| 474 |
-
# ---- occupancy
|
| 475 |
-
# at every (threads, dyn_smem) point; when the forced blocks/SM stop
|
| 476 |
-
# covering memory latency, achieved bandwidth falls off the cliff
|
| 477 |
occ = {}
|
| 478 |
for threads in (128, 256, 512, 1024):
|
| 479 |
row = {}
|
|
@@ -494,8 +617,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 494 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 495 |
knee_iters, sink)
|
| 496 |
ms = _median(_events(
|
| 497 |
-
lambda t=threads,
|
| 498 |
-
knee_blocks, t,
|
| 499 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 500 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 501 |
knees[f"t{threads}"] = row
|
|
@@ -504,27 +627,160 @@ def probe_device(device="cuda", quick=False):
|
|
| 504 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 505 |
"the cliff marks where occupancy stops covering latency",
|
| 506 |
clk, "high", "timing-derived counterpart to the API table above")
|
|
|
|
|
|
|
|
|
|
| 507 |
return d
|
| 508 |
|
| 509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 511 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 512 |
-
device="cuda"):
|
| 513 |
-
"""Median-of-N cudaEvent timing with throttle rejection.
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
around every sample, ~0.3 ms each."""
|
| 523 |
for _ in range(warmup):
|
| 524 |
fn()
|
| 525 |
_sync()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
graph_state = "off"
|
| 527 |
graph_reason = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
if graph:
|
| 529 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 530 |
try:
|
|
@@ -539,10 +795,7 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 539 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 540 |
graph_state = "fallback_eager"
|
| 541 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 542 |
-
# drain the invalidated stream
|
| 543 |
-
# On some torch versions a failed capture also wedges the
|
| 544 |
-
# device RNG with no in-process recovery; timing uses no RNG,
|
| 545 |
-
# but the caller's later randn may raise.
|
| 546 |
rng_ok = True
|
| 547 |
for _attempt in range(5):
|
| 548 |
try:
|
|
@@ -572,25 +825,51 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 572 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 573 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 574 |
best_clk = max(clocks)
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
flat = []
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
kept.append(ms)
|
| 585 |
else:
|
| 586 |
rejected += 1
|
| 587 |
-
|
|
|
|
| 588 |
kept = flat
|
| 589 |
med = _median(kept)
|
| 590 |
ks = sorted(kept)
|
| 591 |
n_k = len(ks)
|
| 592 |
-
# order-statistic 95% CI on the median
|
| 593 |
-
# binomial ranks); degenerate at tiny n, where it spans the sample
|
| 594 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 595 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 596 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
@@ -605,12 +884,25 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 605 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 606 |
"samples": len(flat),
|
| 607 |
"rejected_throttled": rejected,
|
|
|
|
| 608 |
"graph_captured": graph_state == "captured",
|
| 609 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 610 |
"worst": round(min(clocks), 3)},
|
| 611 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
if graph_reason:
|
| 613 |
rep["graph_fallback_reason"] = graph_reason
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
if bytes is not None:
|
| 615 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 616 |
if flops is not None:
|
|
@@ -620,6 +912,8 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 620 |
if dossier is not None and bytes and flops:
|
| 621 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 622 |
mma_v = dossier["mma"]["value"].get(dtype)
|
|
|
|
|
|
|
| 623 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 624 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 625 |
ai = flops / bytes
|
|
@@ -630,13 +924,12 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 630 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 631 |
"peak_dtype": dtype,
|
| 632 |
}
|
|
|
|
| 633 |
return rep
|
| 634 |
|
| 635 |
|
| 636 |
def _to_ordered_int(t):
|
| 637 |
-
"""
|
| 638 |
-
distance: positive floats keep their bits, negative floats mirror
|
| 639 |
-
below zero, so the mapping is monotone in the float value."""
|
| 640 |
if t.dtype == torch.float32:
|
| 641 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 642 |
half = 1 << 31
|
|
@@ -652,11 +945,12 @@ def _to_ordered_int(t):
|
|
| 652 |
|
| 653 |
|
| 654 |
def ulp_diff(a, b):
|
| 655 |
-
"""Elementwise ULP distance
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
|
|
|
| 660 |
if not a.is_floating_point():
|
| 661 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 662 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
@@ -673,9 +967,7 @@ class order_shuffles:
|
|
| 673 |
|
| 674 |
@staticmethod
|
| 675 |
def last_dim():
|
| 676 |
-
"""
|
| 677 |
-
accumulation order of a last-dim reduction changes, the value
|
| 678 |
-
does not."""
|
| 679 |
def shuffle(args, seed):
|
| 680 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 681 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
@@ -685,8 +977,7 @@ class order_shuffles:
|
|
| 685 |
|
| 686 |
@staticmethod
|
| 687 |
def matmul_k():
|
| 688 |
-
"""
|
| 689 |
-
product is unchanged, the accumulation order is not."""
|
| 690 |
def shuffle(args, seed):
|
| 691 |
a_m, b_m = args[0], args[1]
|
| 692 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
@@ -700,37 +991,70 @@ def _as_outputs(x):
|
|
| 700 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 701 |
|
| 702 |
|
| 703 |
-
def
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
worst = {"max_ulp": -1}
|
| 722 |
total_hist = {}
|
| 723 |
per_trial = []
|
| 724 |
max_abs_seen = 0.0
|
| 725 |
max_rel_seen = 0.0
|
|
|
|
| 726 |
for t in range(trials):
|
| 727 |
args = gen(seed + t)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
r_outs = _as_outputs(ref(*args))
|
| 729 |
c_outs = _as_outputs(cand(*args))
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
bands = None
|
| 735 |
band = 0
|
| 736 |
band_score = 0.0
|
|
@@ -754,8 +1078,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 754 |
max_ulp = 0
|
| 755 |
mean_ulp = 0.0
|
| 756 |
first_div = -1
|
|
|
|
| 757 |
n_el = 0
|
| 758 |
n_above_band = 0
|
|
|
|
|
|
|
| 759 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 760 |
worst_elements = []
|
| 761 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
@@ -773,8 +1100,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 773 |
mean_ulp += float(d.double().sum().item())
|
| 774 |
n_el += d.numel()
|
| 775 |
nz = (d > 0)
|
|
|
|
|
|
|
| 776 |
if first_div < 0 and nz.any():
|
| 777 |
first_div = int(nz.flatten().nonzero()[0].item())
|
|
|
|
| 778 |
if nz.any():
|
| 779 |
buckets = torch.clamp(
|
| 780 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
@@ -791,6 +1121,7 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 791 |
if val > 0:
|
| 792 |
worst_elements.append({
|
| 793 |
"output": oi, "index": int(ix),
|
|
|
|
| 794 |
"ref": float(rf[ix].item()),
|
| 795 |
"cand": float(cf[ix].item()),
|
| 796 |
"ulp": int(val)})
|
|
@@ -810,23 +1141,27 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 810 |
inf_r += int(torch.isinf(r).sum().item())
|
| 811 |
inf_c += int(torch.isinf(c).sum().item())
|
| 812 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
|
|
|
| 813 |
trial_rep = {
|
| 814 |
"max_ulp": max_ulp,
|
| 815 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 816 |
"first_divergent_index": first_div,
|
|
|
|
| 817 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 818 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 819 |
"order_band_ulp": band,
|
| 820 |
"band_score": round(band_score, 2),
|
| 821 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
|
|
|
|
|
|
| 822 |
"worst_elements": worst_elements[:worst_k],
|
| 823 |
}
|
| 824 |
per_trial.append(trial_rep)
|
| 825 |
if max_ulp > worst["max_ulp"]:
|
| 826 |
worst = trial_rep
|
| 827 |
-
band_max = max(t["order_band_ulp"] for t in per_trial)
|
| 828 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 829 |
-
|
|
|
|
| 830 |
score = max(t["band_score"] for t in per_trial)
|
| 831 |
ulp_ok = score <= band_margin
|
| 832 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
@@ -841,8 +1176,34 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 841 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 842 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 843 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 844 |
-
|
| 845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
"criterion": criterion,
|
| 847 |
"worst_trial": worst,
|
| 848 |
"max_abs": max_abs_seen,
|
|
@@ -850,6 +1211,9 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 850 |
"trials": per_trial,
|
| 851 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 852 |
}
|
|
|
|
|
|
|
|
|
|
| 853 |
|
| 854 |
|
| 855 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
@@ -865,6 +1229,30 @@ def _context_alive(device):
|
|
| 865 |
return False
|
| 866 |
|
| 867 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
def _fuzz_build(make, shape, dtype):
|
| 869 |
try:
|
| 870 |
out = make(tuple(shape), dtype)
|
|
@@ -881,7 +1269,7 @@ def _fuzz_variant(x, v):
|
|
| 881 |
if v == "strided_rows":
|
| 882 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 883 |
if v == "offset_slice":
|
| 884 |
-
#
|
| 885 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 886 |
if v == "overflow_expand":
|
| 887 |
if x.numel() == 0:
|
|
@@ -895,8 +1283,7 @@ def _fuzz_variant(x, v):
|
|
| 895 |
|
| 896 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 897 |
stop_on_context_loss=True):
|
| 898 |
-
"""All variant cases for one (shape, dtype) group; returns (rows,
|
| 899 |
-
context_lost)."""
|
| 900 |
rows = []
|
| 901 |
bases = _fuzz_build(make, shape, dtype)
|
| 902 |
lost = False
|
|
@@ -933,8 +1320,7 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 933 |
got = op(*xs)
|
| 934 |
want = ref(*xs)
|
| 935 |
if v == "overflow_expand":
|
| 936 |
-
# bounded comparison:
|
| 937 |
-
# which is where int32 flat indexing wraps
|
| 938 |
w = 1 << 20
|
| 939 |
gf, wf = got.flatten(), want.flatten()
|
| 940 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
@@ -959,20 +1345,30 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 959 |
return rows, lost
|
| 960 |
|
| 961 |
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
import
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 974 |
meta = json.load(open(sys.argv[1], "r"))
|
| 975 |
-
sys.path[:0] = meta["sys_path"]
|
| 976 |
try:
|
| 977 |
import kernels
|
| 978 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
@@ -985,85 +1381,165 @@ except Exception:
|
|
| 985 |
import load_local
|
| 986 |
_mk = load_local.load()
|
| 987 |
import torch
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 994 |
"""
|
| 995 |
|
| 996 |
|
| 997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 999 |
-
isolate_timeout_s=180, device="cuda"):
|
| 1000 |
-
"""
|
| 1001 |
-
report ok / mismatch / exception per case.
|
| 1002 |
-
|
| 1003 |
-
`make(shape, dtype)` (or `make(shape)`) builds a tensor or tuple;
|
| 1004 |
-
layout variants derive non-contiguous and misaligned views whose
|
| 1005 |
-
content equals a contiguous copy. `overflow_expand` stride-0-expands
|
| 1006 |
-
past 2**31 logical elements to catch int32 indexing (`skipped_vram`
|
| 1007 |
-
when it would not fit). Every case carries a `repro` recipe.
|
| 1008 |
-
In-process, the CUDA context is health-checked after exceptions and
|
| 1009 |
-
remaining cases marked `context_lost` if killed; `isolate=True`
|
| 1010 |
-
spawns each (shape, dtype) group in a subprocess, so a
|
| 1011 |
-
context-killing candidate costs one group (`crashed_isolated`);
|
| 1012 |
-
op/ref/make must be picklable."""
|
| 1013 |
if shapes is None:
|
| 1014 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1015 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
|
|
|
|
|
|
| 1016 |
|
| 1017 |
rows = []
|
| 1018 |
lost = False
|
| 1019 |
if isolate:
|
| 1020 |
import json
|
| 1021 |
import pickle
|
| 1022 |
-
import subprocess
|
| 1023 |
import sys as _sys
|
| 1024 |
import tempfile
|
| 1025 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1026 |
fns = os.path.join(tmpdir, "fns.pkl")
|
|
|
|
|
|
|
| 1027 |
with open(fns, "wb") as f:
|
| 1028 |
-
pickle.dump(
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
"shape": list(shape),
|
| 1041 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1042 |
"variants": list(variants),
|
| 1043 |
"ulp_tol": ulp_tol, "device": device,
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
try:
|
| 1048 |
-
r = subprocess.run(
|
| 1049 |
-
[_sys.executable, "-c", _ISOLATE_BOOTSTRAP, meta,
|
| 1050 |
-
fns, out], timeout=isolate_timeout_s,
|
| 1051 |
-
capture_output=True)
|
| 1052 |
-
if r.returncode == 0 and os.path.exists(out):
|
| 1053 |
with open(out, "rb") as f:
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
"status": "crashed_isolated",
|
| 1064 |
-
"exitcode": exitcode,
|
| 1065 |
-
"stderr_tail": stderr_tail}]
|
| 1066 |
-
rows.extend(group)
|
| 1067 |
else:
|
| 1068 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1069 |
for gi, (dtype, shape) in enumerate(pending):
|
|
@@ -1080,21 +1556,27 @@ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
|
|
| 1080 |
n_bad = sum(1 for r in rows
|
| 1081 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1082 |
"skipped_empty"))
|
| 1083 |
-
|
| 1084 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
|
| 1086 |
|
| 1087 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1088 |
check=None, budget_s=None, race=False):
|
| 1089 |
-
"""Launch-configuration search
|
| 1090 |
-
bench row per grid point, sorted fastest first. A point whose factory
|
| 1091 |
-
or run raises is reported, not skipped silently. `check(fn) -> bool`
|
| 1092 |
-
is an optional correctness gate recorded per row (a fast wrong config
|
| 1093 |
-
must not win a sweep). `budget_s` caps total sweep time; points not
|
| 1094 |
-
reached are reported as `not_run` rather than silently dropped.
|
| 1095 |
-
`race=True` runs successive halving: a quick pass over every point at
|
| 1096 |
-
iters/4, then the full budget only on the fastest quartile; pruned
|
| 1097 |
-
points keep their quick measurement and status `pruned_race`."""
|
| 1098 |
keys = list(grid.keys())
|
| 1099 |
|
| 1100 |
def expand(i, acc):
|
|
@@ -1159,13 +1641,71 @@ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
|
| 1159 |
continue
|
| 1160 |
rows.append(run_point(p, iters))
|
| 1161 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
return rows
|
| 1163 |
|
| 1164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1165 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1166 |
-
"""Device buffer for
|
| 1167 |
-
stamps.cuh): the candidate kernel writes clock64() into slot i. With
|
| 1168 |
-
`blocks`, allocates the [blocks, n] layout for MK_STAMP_BLOCK."""
|
| 1169 |
if blocks is not None:
|
| 1170 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1171 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
@@ -1175,8 +1715,7 @@ _DRIFT_SKIP = ("device", "clock", "launch_overhead", "transfer_bw")
|
|
| 1175 |
|
| 1176 |
|
| 1177 |
def pinned_dossiers():
|
| 1178 |
-
"""Names of the
|
| 1179 |
-
architecture)."""
|
| 1180 |
return sorted(_PINNED.keys())
|
| 1181 |
|
| 1182 |
|
|
@@ -1195,11 +1734,7 @@ def _numeric_leaves(d, prefix=""):
|
|
| 1195 |
|
| 1196 |
|
| 1197 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1198 |
-
"""
|
| 1199 |
-
same architecture: per-leaf relative drift, worst drift, entries
|
| 1200 |
-
over `rel_tol`. Host-dependent entries and raw clocks are skipped by
|
| 1201 |
-
default (`skip=()` compares everything); `pinned=None` auto-selects
|
| 1202 |
-
by sm_arch and raises if no pin exists."""
|
| 1203 |
if pinned is None:
|
| 1204 |
arch = current["device"]["sm_arch"]
|
| 1205 |
matches = [k for k, v in _PINNED.items()
|
|
@@ -1222,16 +1757,19 @@ def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
|
| 1222 |
"rel": round(rel, 4)})
|
| 1223 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1224 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
|
| 1229 |
|
| 1230 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1231 |
device="cuda"):
|
| 1232 |
-
"""Run the shipped MK_STAMP_BLOCK example
|
| 1233 |
-
compute, global store) and decode its stamps; a template for
|
| 1234 |
-
instrumenting a candidate."""
|
| 1235 |
props = torch.cuda.get_device_properties(device)
|
| 1236 |
if blocks is None:
|
| 1237 |
blocks = props.multi_processor_count
|
|
@@ -1244,11 +1782,7 @@ def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
|
| 1244 |
|
| 1245 |
|
| 1246 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1247 |
-
"""Decode MK_STAMP
|
| 1248 |
-
microseconds under the measured (or provided) SM clock. A 1-D buffer
|
| 1249 |
-
(MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
|
| 1250 |
-
buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
|
| 1251 |
-
phase, with blocks whose stamps stayed zero excluded."""
|
| 1252 |
if clock_ghz is None:
|
| 1253 |
clock_ghz = measure_clock(stamps.device)
|
| 1254 |
|
|
@@ -1284,3 +1818,104 @@ def read_stamps(stamps, labels=None, clock_ghz=None):
|
|
| 1284 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1285 |
"blocks_reporting": int(live.sum().item()),
|
| 1286 |
"blocks_total": int(live.numel())}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Measuring instrument for the kernel-design loop: measured device facts, throttle-aware timing, adversarial correctness comparison, all as loadable ops returning plain data."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import math
|
| 3 |
import os
|
| 4 |
import time
|
|
|
|
| 8 |
try:
|
| 9 |
from ._ops import ops
|
| 10 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 11 |
+
# children re-import the package; a source checkout rebuilds the op table from the cached JIT extension
|
|
|
|
|
|
|
| 12 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 13 |
if not _root:
|
| 14 |
raise
|
|
|
|
| 30 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 31 |
_PINNED = {}
|
| 32 |
|
| 33 |
+
__all__ = ["probe_device", "probe_all_devices", "bench", "compare", "judge",
|
| 34 |
+
"order_shuffles", "fuzz_shapes", "sweep", "alloc_stamps",
|
| 35 |
+
"read_stamps", "stamped_demo", "measure_clock", "ulp_diff",
|
| 36 |
+
"dossier_drift", "pinned_dossiers", "mma_tile_check", "schema",
|
| 37 |
+
"probe_capturable", "ops"]
|
| 38 |
+
|
| 39 |
+
_REPO_ID = "phanerozoic/metakernel"
|
| 40 |
+
_REPO_VERSION = 1
|
| 41 |
+
_PROBE_SCHEMA = 2
|
| 42 |
+
|
| 43 |
+
# kind id, C-tile M*N, nominal K, nominal K-lanes per unrolled loop iteration
|
| 44 |
_MMA_KINDS = {
|
| 45 |
+
"fp16": (0, 16 * 16, 16, 64.0),
|
| 46 |
+
"bf16": (1, 16 * 16, 16, 64.0),
|
| 47 |
+
"tf32": (2, 16 * 16, 8, 32.0),
|
| 48 |
+
"int8": (3, 16 * 16, 16, 64.0),
|
| 49 |
+
"fp8_e4m3": (4, 16 * 8, 32, 256.0),
|
| 50 |
+
"fp16_sparse_2_4": (5, 16 * 8, 32, 128.0),
|
| 51 |
+
"fp8_e5m2": (6, 16 * 8, 32, 256.0),
|
| 52 |
}
|
| 53 |
|
| 54 |
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def _events(fn, reps):
|
| 60 |
+
"""Time reps calls of fn with one cudaEvent pair each; returns a ms list."""
|
| 61 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 62 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 63 |
for s, e in zip(starts, stops):
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 72 |
+
"""Effective SM clock in GHz from clock64 ticks over wall time under an FMA spin."""
|
|
|
|
| 73 |
props = torch.cuda.get_device_properties(device)
|
| 74 |
if blocks is None:
|
| 75 |
blocks = props.multi_processor_count
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
def _sattolo_ring(n, device, generator=None):
|
| 105 |
+
"""Random single-cycle permutation as an int32 next-pointer table."""
|
|
|
|
| 106 |
perm = torch.randperm(n, generator=generator)
|
| 107 |
ring = torch.empty(n, dtype=torch.int32)
|
| 108 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
|
|
| 110 |
|
| 111 |
|
| 112 |
def _smi_settings(index):
|
| 113 |
+
"""ECC mode, driver, power limit via nvidia-smi; None fields when unavailable."""
|
|
|
|
| 114 |
import subprocess
|
| 115 |
try:
|
| 116 |
out = subprocess.run(
|
|
|
|
| 130 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 131 |
|
| 132 |
|
| 133 |
+
def _unravel(flat, shape):
|
| 134 |
+
"""Multi-dimensional index for a flat index into shape."""
|
| 135 |
+
if not shape:
|
| 136 |
+
return []
|
| 137 |
+
out = []
|
| 138 |
+
for s in reversed(list(shape)):
|
| 139 |
+
out.append(int(flat % s))
|
| 140 |
+
flat //= s
|
| 141 |
+
return list(reversed(out))
|
| 142 |
+
|
| 143 |
+
|
| 144 |
def mma_tile_check(device="cuda", trials=8):
|
| 145 |
+
"""Random small-integer m16n16k16 tiles through the wmma path, compared exactly to the reference GEMM."""
|
|
|
|
|
|
|
| 146 |
worst = 0.0
|
| 147 |
for t in range(trials):
|
| 148 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
|
|
| 160 |
"trials": trials}
|
| 161 |
|
| 162 |
|
| 163 |
+
def _tenancy_canary(device, rel_tol=0.10, attempts=3):
|
| 164 |
+
"""Fixed-cost triad timed in paired rounds; busy only when every attempt disagrees."""
|
| 165 |
+
n4 = 4 << 20
|
| 166 |
+
a = torch.rand(n4 * 4, device=device)
|
| 167 |
+
b = torch.rand(n4 * 4, device=device)
|
| 168 |
+
c = torch.empty(n4 * 4, device=device)
|
| 169 |
+
ops.mk_triad(a, b, c, 4, 1.5)
|
| 170 |
+
rels = []
|
| 171 |
+
for _ in range(attempts):
|
| 172 |
+
r1 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 173 |
+
r2 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 174 |
+
rel = abs(r1 - r2) / min(r1, r2)
|
| 175 |
+
rels.append(round(rel, 4))
|
| 176 |
+
if rel <= rel_tol:
|
| 177 |
+
return False, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 178 |
+
return True, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _cache_dir():
|
| 182 |
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~/.cache")
|
| 183 |
+
d = os.path.join(base, "metakernel")
|
| 184 |
+
os.makedirs(d, exist_ok=True)
|
| 185 |
+
return d
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _cache_path(device, quick):
|
| 189 |
+
import re
|
| 190 |
+
props = torch.cuda.get_device_properties(device)
|
| 191 |
+
smi = _smi_settings(torch.device(device).index or 0)
|
| 192 |
+
slug = re.sub(r"[^a-z0-9]+", "", props.name.lower())[:24]
|
| 193 |
+
key = "sm%d%d_%s_drv%s_cu%s_q%d_s%d" % (
|
| 194 |
+
props.major, props.minor, slug, smi["driver"] or "na",
|
| 195 |
+
torch.version.cuda, int(quick), _PROBE_SCHEMA)
|
| 196 |
+
return os.path.join(_cache_dir(), key + ".json")
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _mma_calibrate(kind_id, sink):
|
| 200 |
+
"""Executed K-lanes per loop iteration from a sub-saturation accumulator read-back, with a linearity check."""
|
| 201 |
+
gains = []
|
| 202 |
+
for cal in (4, 32):
|
| 203 |
+
sink[1] = 0.0
|
| 204 |
+
ops.mk_mma(kind_id, 1, 1, cal, sink)
|
| 205 |
+
_sync()
|
| 206 |
+
gains.append(float(sink[1].item()) / cal)
|
| 207 |
+
linear = gains[0] > 0 and abs(gains[1] - gains[0]) / gains[0] < 0.02
|
| 208 |
+
return gains[0], linear
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _fp8_accum_stall(kind_id, sink, k_exec):
|
| 212 |
+
"""Growth curve of the fp8 accumulator; a stall reveals the effective accumulate mantissa width."""
|
| 213 |
+
vals = []
|
| 214 |
+
for it in (32, 64, 128, 256, 512):
|
| 215 |
+
sink[1] = 0.0
|
| 216 |
+
ops.mk_mma(kind_id, 1, 1, it, sink)
|
| 217 |
+
_sync()
|
| 218 |
+
vals.append(float(sink[1].item()) / 2.0)
|
| 219 |
+
if vals[-1] == vals[-2] and vals[-1] < vals[0] * 16 * 0.99:
|
| 220 |
+
bits = int(round(math.log2(vals[-1] / (2.0 * k_exec))))
|
| 221 |
+
return {"stall_element_value": vals[-1],
|
| 222 |
+
"effective_accum_mantissa_bits": bits}
|
| 223 |
+
return {"stall_element_value": None,
|
| 224 |
+
"effective_accum_mantissa_bits": None}
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def probe_device(device="cuda", quick=False, cached=True,
|
| 228 |
+
tenancy_check=True):
|
| 229 |
+
"""Empirical device dossier; every entry measured by a dedicated probe kernel and stamped with its SM clock."""
|
| 230 |
+
cpath = _cache_path(device, quick)
|
| 231 |
+
if cached and os.path.exists(cpath):
|
| 232 |
+
import json
|
| 233 |
+
return json.load(open(cpath, encoding="utf-8"))
|
| 234 |
+
if tenancy_check:
|
| 235 |
+
busy, ev = _tenancy_canary(device)
|
| 236 |
+
if busy:
|
| 237 |
+
raise RuntimeError("device_busy: canary rounds disagree %s; "
|
| 238 |
+
"idle the device or pass "
|
| 239 |
+
"tenancy_check=False" % ev)
|
| 240 |
props = torch.cuda.get_device_properties(device)
|
| 241 |
sm = props.multi_processor_count
|
| 242 |
l2 = props.L2_cache_size
|
|
|
|
| 249 |
"vram_bytes": props.total_memory,
|
| 250 |
"torch": torch.__version__,
|
| 251 |
"cuda_runtime": torch.version.cuda,
|
| 252 |
+
# instrument settings recorded because readings depend on them
|
|
|
|
| 253 |
**_smi_settings(torch.device(device).index or 0),
|
| 254 |
}
|
| 255 |
}
|
|
|
|
| 274 |
}
|
| 275 |
clk = max(traj)
|
| 276 |
|
| 277 |
+
# ---- device-memory bandwidth per access width plus read/write/gather
|
| 278 |
+
n4 = 16 << 20
|
|
|
|
| 279 |
if props.total_memory < 4 << 30:
|
| 280 |
n4 = 4 << 20
|
| 281 |
a = torch.rand(n4 * 4, device=device)
|
|
|
|
| 299 |
dtype=torch.int32).to(device)
|
| 300 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 301 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
|
|
|
| 302 |
hbm["random_gather_16B"] = round(
|
| 303 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 304 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
|
|
| 309 |
"so its nominal number is the achievable rate for "
|
| 310 |
"random access, not a line-efficiency claim")
|
| 311 |
|
| 312 |
+
# ---- L2 bandwidth over an L2-resident working set
|
| 313 |
ws = max(l2 // 4, 1 << 20)
|
| 314 |
+
nl2 = ws // 16
|
| 315 |
al = a[:nl2 * 4].contiguous()
|
| 316 |
bl = b[:nl2 * 4].contiguous()
|
| 317 |
cl = c[:nl2 * 4].contiguous()
|
|
|
|
| 326 |
"medium", "first pass streams from HBM; amortized "
|
| 327 |
f"over {passes} passes")
|
| 328 |
|
| 329 |
+
# ---- latencies: min of 3 chases per level so a co-tenant burst cannot land in the dossier
|
|
|
|
|
|
|
| 330 |
lat = {}
|
| 331 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
+
def chase(op, ring, warm_hops, hops, extra=None):
|
| 334 |
+
best = None
|
| 335 |
+
for _ in range(3):
|
| 336 |
+
if extra is None:
|
| 337 |
+
op(ring, warm_hops, out64)
|
| 338 |
+
_sync()
|
| 339 |
+
op(ring, hops, out64)
|
| 340 |
+
else:
|
| 341 |
+
op(ring, extra, warm_hops, out64)
|
| 342 |
+
_sync()
|
| 343 |
+
op(ring, extra, hops, out64)
|
| 344 |
+
_sync()
|
| 345 |
+
t = int(out64[0].item()) / hops
|
| 346 |
+
best = t if best is None else min(best, t)
|
| 347 |
+
return best
|
|
|
|
|
|
|
| 348 |
|
| 349 |
+
ring_s = _sattolo_ring(8192, device)
|
| 350 |
+
lat["smem_cycles"] = round(
|
| 351 |
+
chase(lambda r, h, o: ops.mk_chase_shared(r, h, o), ring_s,
|
| 352 |
+
1 << 14, 1 << 16), 1)
|
| 353 |
+
ring_l1 = _sattolo_ring((16 << 10) // 4, device)
|
| 354 |
+
lat["l1_cycles"] = round(
|
| 355 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l1,
|
| 356 |
+
ring_l1.numel(), 1 << 15), 1)
|
| 357 |
+
ring_l2 = _sattolo_ring((2 << 20) // 4, device)
|
| 358 |
+
lat["l2_cycles"] = round(
|
| 359 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l2,
|
| 360 |
+
ring_l2.numel(), 1 << 14), 1)
|
| 361 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 362 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 363 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 364 |
+
lat["dram_cycles"] = round(
|
| 365 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_h,
|
| 366 |
+
1 << 14, hops_h), 1)
|
|
|
|
|
|
|
| 367 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 368 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 369 |
+
"Sattolo-cycle pointer chase, min of 3 runs; 16 "
|
| 370 |
+
"KB ring for L1, 2 MB TLB-resident ring for L2, "
|
| 371 |
+
"self-evicting chain (2x L2 in touched lines) "
|
| 372 |
+
"for HBM", idle_clk, "high",
|
| 373 |
"smem includes index arithmetic; HBM includes "
|
| 374 |
"residual TLB effects at large footprints")
|
| 375 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 376 |
|
| 377 |
+
# ---- fp32/fp64 FMA and tensor-core throughput
|
| 378 |
blocks = sm * 4
|
| 379 |
iters = 20_000 if quick else 60_000
|
| 380 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 381 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 382 |
reps))
|
| 383 |
+
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 384 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 385 |
"register-resident FMA chains", clk, "high")
|
| 386 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
|
|
| 392 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 393 |
"register-resident double FMA chains (the DFMA "
|
| 394 |
"pipe)", clk, "high")
|
| 395 |
+
|
| 396 |
+
# ---- ILP ladder: 1/2/4 chains at low occupancy separates latency-bound from pipe-bound
|
| 397 |
+
ilp = {}
|
| 398 |
+
it_ilp = 20_000 if quick else 60_000
|
| 399 |
+
for ch in (1, 2, 4):
|
| 400 |
+
ops.mk_fma_ilp(ch, sm, 64, it_ilp, sink)
|
| 401 |
+
ms = _median(_events(
|
| 402 |
+
lambda c2=ch: ops.mk_fma_ilp(c2, sm, 64, it_ilp, sink), reps))
|
| 403 |
+
ilp[f"ilp{ch}"] = round(
|
| 404 |
+
sm * 64 * it_ilp * 16 * 2 * ch / (ms * 1e-3) / 1e12, 2)
|
| 405 |
+
ilp["ratio_4_1"] = round(ilp["ilp4"] / ilp["ilp1"], 2)
|
| 406 |
+
d["fma_ilp"] = _entry(ilp, "TFLOP/s",
|
| 407 |
+
"1/2/4 independent dependent FMA chains per "
|
| 408 |
+
"thread at one block of 64 threads per SM; a "
|
| 409 |
+
"ratio near 4 means latency-bound at that "
|
| 410 |
+
"occupancy", clk, "high")
|
| 411 |
+
|
| 412 |
mma = {}
|
| 413 |
mma_klanes = {}
|
| 414 |
+
fp8_accum = {}
|
| 415 |
+
for name, (kind, mn, k_nom, nom_lanes) in _MMA_KINDS.items():
|
| 416 |
it = 30_000 if quick else 100_000
|
| 417 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 418 |
if r != 0:
|
| 419 |
mma[name] = "unsupported"
|
| 420 |
continue
|
| 421 |
_sync()
|
| 422 |
+
gain, linear = _mma_calibrate(kind, sink)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
mma_klanes[name] = {
|
| 424 |
+
"executed_k_per_iter": round(gain, 2),
|
| 425 |
+
"vs_nominal": round(gain / nom_lanes, 3),
|
| 426 |
+
"calibration_linear": linear,
|
| 427 |
}
|
| 428 |
+
if name.startswith("fp8"):
|
| 429 |
+
fp8_accum[name] = _fp8_accum_stall(kind, sink, k_nom)
|
| 430 |
ms = _median(_events(
|
| 431 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 432 |
+
tflops = blocks * 8 * it * 2.0 * mn * gain / (ms * 1e-3) / 1e12
|
| 433 |
+
if name == "fp16_sparse_2_4":
|
| 434 |
+
mma[name] = {"executed": round(tflops, 1),
|
| 435 |
+
"dense_equivalent": round(2 * tflops, 1)}
|
| 436 |
+
else:
|
| 437 |
+
mma[name] = round(tflops, 1)
|
| 438 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 439 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 440 |
+
"2:4-sparse fp16 via inline PTX m16n8k32); flops "
|
| 441 |
+
"derive from a sub-saturation accumulator read-back "
|
|
|
|
| 442 |
"with every fragment element observed, never the "
|
| 443 |
+
"declared loop shape; sparse reports executed and "
|
| 444 |
+
"dense-equivalent flops",
|
| 445 |
clk, "medium",
|
| 446 |
"dense-tile rate without memory traffic; real "
|
| 447 |
"kernels see this only when staging hides behind it")
|
| 448 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
| 449 |
+
if fp8_accum:
|
| 450 |
+
d["mma"]["fp8_accum"] = fp8_accum
|
| 451 |
|
| 452 |
+
# ---- fed MMA: smem-staged fp16 tiles under cp.async double buffering
|
| 453 |
+
if props.major >= 8:
|
| 454 |
+
gbuf = torch.ones(16 << 20, dtype=torch.float16, device=device)
|
| 455 |
+
sink[1] = 0.0
|
| 456 |
+
r = ops.mk_mma_fed(gbuf, 1, 1, 64, sink)
|
| 457 |
+
if r == 0:
|
| 458 |
+
_sync()
|
| 459 |
+
fed_gain = float(sink[1].item()) / 64
|
| 460 |
+
it_fed = 6_000 if quick else 20_000
|
| 461 |
+
ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink)
|
| 462 |
+
ms = _median(_events(
|
| 463 |
+
lambda: ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink),
|
| 464 |
+
reps))
|
| 465 |
+
fed = sm * 2 * 8 * it_fed * 2.0 * 256 * fed_gain / \
|
| 466 |
+
(ms * 1e-3) / 1e12
|
| 467 |
+
d["mma_fed"] = _entry(
|
| 468 |
+
round(fed, 1), "TFLOP/s",
|
| 469 |
+
"m16n16k16 fp16 tiles fed from an L2-resident buffer "
|
| 470 |
+
"through cp.async double-buffered shared memory; "
|
| 471 |
+
"accumulator-verified", clk, "high",
|
| 472 |
+
"the achieved counterpart to the register peak: staging "
|
| 473 |
+
"exposed, one dependent accumulator chain per warp")
|
| 474 |
+
del gbuf
|
| 475 |
+
|
| 476 |
+
# ---- shared-memory bandwidth and the bank-conflict stride sweep
|
| 477 |
it_sm = 30_000 if quick else 100_000
|
| 478 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 479 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
|
|
| 483 |
"block-resident read+write streaming (8 B per "
|
| 484 |
"thread-iteration), aggregated across SMs", clk,
|
| 485 |
"medium",
|
| 486 |
+
"bank-conflict-light access pattern; the stride "
|
| 487 |
+
"sweep below prices the conflicted case")
|
| 488 |
+
strides = (1, 8, 32) if quick else (1, 2, 4, 8, 16, 32)
|
| 489 |
+
it_st = 8_000 if quick else 20_000
|
| 490 |
+
st_ms = {}
|
| 491 |
+
for s2 in strides:
|
| 492 |
+
ops.mk_smem_stride(s2, blocks, 256, it_st, sink)
|
| 493 |
+
st_ms[s2] = _median(_events(
|
| 494 |
+
lambda s3=s2: ops.mk_smem_stride(s3, blocks, 256, it_st, sink),
|
| 495 |
+
reps))
|
| 496 |
+
d["smem_conflict_penalty"] = _entry(
|
| 497 |
+
{f"stride_{s2}": round(st_ms[s2] / st_ms[1], 2) for s2 in strides},
|
| 498 |
+
"x (time ratio vs stride 1)",
|
| 499 |
+
"dependent read-FMA-write per thread at warp-lane bank distance "
|
| 500 |
+
"`stride`; stride 32 puts every lane in one bank", clk, "high",
|
| 501 |
+
"the direct conflict measurement that disambiguates a "
|
| 502 |
+
"bank-conflict signature from an ILP stall")
|
| 503 |
|
| 504 |
# ---- PCIe / host transfer bandwidth
|
| 505 |
nbytes = 256 << 20
|
|
|
|
| 530 |
"d2d counts one direction of traffic")
|
| 531 |
del host_pin, host_pag, dev_t, dev_u
|
| 532 |
|
| 533 |
+
# ---- atomic contention curve
|
|
|
|
|
|
|
| 534 |
n_thr = blocks * 256
|
| 535 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 536 |
at = {"curve_slots": {}}
|
|
|
|
| 540 |
buf.zero_()
|
| 541 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 542 |
ms = _median(_events(
|
| 543 |
+
lambda s4=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s4),
|
| 544 |
reps))
|
| 545 |
at["curve_slots"][str(slots)] = round(
|
| 546 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
|
|
| 553 |
"of distinct global addresses from 1 (full "
|
| 554 |
"contention) to one per thread", clk, "high")
|
| 555 |
|
| 556 |
+
# ---- launch overhead: torch-dispatch path and the raw C++ path
|
| 557 |
anchor = sink
|
| 558 |
n_launch = 500 if quick else 2000
|
| 559 |
_sync()
|
|
|
|
| 568 |
ops.mk_empty(anchor)
|
| 569 |
_sync()
|
| 570 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
| 571 |
+
raw_q = float(ops.mk_launch_raw(n_launch, 0, anchor))
|
| 572 |
+
raw_rt = float(ops.mk_launch_raw(n_rt, 1, anchor))
|
| 573 |
d["launch_overhead"] = _entry(
|
| 574 |
+
{"torch_queued_us": round(queued, 2),
|
| 575 |
+
"torch_round_trip_us": round(rt, 2),
|
| 576 |
+
"raw_queued_us": round(raw_q / n_launch * 1e6, 2)
|
| 577 |
+
if raw_q >= 0 else None,
|
| 578 |
+
"raw_round_trip_us": round(raw_rt / n_rt * 1e6, 2)
|
| 579 |
+
if raw_rt >= 0 else None},
|
| 580 |
+
"us", "empty-kernel trains; torch path per-launch through op "
|
| 581 |
+
"dispatch, raw path looped inside one binding call", clk,
|
| 582 |
+
"medium", "host-state-dependent; the torch-minus-raw gap is the "
|
| 583 |
+
"dispatch cost a torch-launched candidate pays")
|
| 584 |
|
| 585 |
# ---- grid barrier
|
| 586 |
bar_iters = 500 if quick else 2000
|
|
|
|
| 596 |
"unavailable or grid too large", clk,
|
| 597 |
"high")
|
| 598 |
|
| 599 |
+
# ---- occupancy API table plus timed knees
|
|
|
|
|
|
|
| 600 |
occ = {}
|
| 601 |
for threads in (128, 256, 512, 1024):
|
| 602 |
row = {}
|
|
|
|
| 617 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 618 |
knee_iters, sink)
|
| 619 |
ms = _median(_events(
|
| 620 |
+
lambda t=threads, s5=smem_kb: ops.mk_fma_dep(
|
| 621 |
+
knee_blocks, t, s5 * 1024, knee_iters, sink), reps))
|
| 622 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 623 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 624 |
knees[f"t{threads}"] = row
|
|
|
|
| 627 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 628 |
"the cliff marks where occupancy stops covering latency",
|
| 629 |
clk, "high", "timing-derived counterpart to the API table above")
|
| 630 |
+
if cached:
|
| 631 |
+
import json
|
| 632 |
+
json.dump(d, open(cpath, "w", encoding="utf-8"))
|
| 633 |
return d
|
| 634 |
|
| 635 |
|
| 636 |
+
def probe_all_devices(quick=True, cached=True, tenancy_check=True):
|
| 637 |
+
"""One dossier per visible device plus a pairwise P2P bandwidth matrix."""
|
| 638 |
+
n = torch.cuda.device_count()
|
| 639 |
+
devices = {}
|
| 640 |
+
for i in range(n):
|
| 641 |
+
devices[i] = probe_device(f"cuda:{i}", quick=quick, cached=cached,
|
| 642 |
+
tenancy_check=tenancy_check)
|
| 643 |
+
p2p = {}
|
| 644 |
+
if n > 1:
|
| 645 |
+
nbytes = 64 << 20
|
| 646 |
+
for i in range(n):
|
| 647 |
+
for j in range(n):
|
| 648 |
+
if i == j:
|
| 649 |
+
continue
|
| 650 |
+
src = torch.rand(nbytes // 4, device=f"cuda:{i}")
|
| 651 |
+
dst = torch.empty(nbytes // 4, device=f"cuda:{j}")
|
| 652 |
+
dst.copy_(src)
|
| 653 |
+
torch.cuda.synchronize(i)
|
| 654 |
+
torch.cuda.synchronize(j)
|
| 655 |
+
t0 = time.perf_counter()
|
| 656 |
+
for _ in range(5):
|
| 657 |
+
dst.copy_(src)
|
| 658 |
+
torch.cuda.synchronize(i)
|
| 659 |
+
torch.cuda.synchronize(j)
|
| 660 |
+
ms = (time.perf_counter() - t0) / 5 * 1e3
|
| 661 |
+
p2p[f"{i}->{j}"] = {
|
| 662 |
+
"gbs": round(nbytes / (ms * 1e-3) / 1e9, 1),
|
| 663 |
+
"peer_access": bool(
|
| 664 |
+
torch.cuda.can_device_access_peer(i, j)),
|
| 665 |
+
}
|
| 666 |
+
del src, dst
|
| 667 |
+
return {"devices": devices, "p2p": p2p}
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
_CAPTURE_PROBE = r"""
|
| 671 |
+
import pickle, sys, torch
|
| 672 |
+
fn = pickle.load(open(sys.argv[1], "rb"))
|
| 673 |
+
for _ in range(3):
|
| 674 |
+
fn()
|
| 675 |
+
torch.cuda.synchronize()
|
| 676 |
+
try:
|
| 677 |
+
g = torch.cuda.CUDAGraph()
|
| 678 |
+
with torch.cuda.graph(g):
|
| 679 |
+
fn()
|
| 680 |
+
print("capturable", flush=True)
|
| 681 |
+
except Exception as e:
|
| 682 |
+
print("uncapturable: %s: %s" % (type(e).__name__, str(e)[:120]),
|
| 683 |
+
flush=True)
|
| 684 |
+
"""
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def probe_capturable(fn, timeout_s=180):
|
| 688 |
+
"""Test CUDA-graph capturability of fn in a child process, so a capture-wedged RNG never reaches the caller."""
|
| 689 |
+
import pickle
|
| 690 |
+
import subprocess
|
| 691 |
+
import sys as _sys
|
| 692 |
+
import tempfile
|
| 693 |
+
try:
|
| 694 |
+
blob = pickle.dumps(fn)
|
| 695 |
+
except Exception: # noqa: BLE001
|
| 696 |
+
return {"capturable": None,
|
| 697 |
+
"reason": "fn is not picklable; probe skipped (define fn "
|
| 698 |
+
"at module top level to enable it)"}
|
| 699 |
+
path = os.path.join(tempfile.mkdtemp(prefix="mk_cap_"), "fn.pkl")
|
| 700 |
+
with open(path, "wb") as f:
|
| 701 |
+
f.write(blob)
|
| 702 |
+
try:
|
| 703 |
+
r = subprocess.run([_sys.executable, "-c", _CAPTURE_PROBE, path],
|
| 704 |
+
capture_output=True, text=True,
|
| 705 |
+
timeout=timeout_s)
|
| 706 |
+
except Exception as e: # noqa: BLE001
|
| 707 |
+
return {"capturable": None,
|
| 708 |
+
"reason": f"probe child failed: {type(e).__name__}"}
|
| 709 |
+
out = (r.stdout or "").strip()
|
| 710 |
+
if out == "capturable":
|
| 711 |
+
return {"capturable": True, "reason": ""}
|
| 712 |
+
if out.startswith("uncapturable"):
|
| 713 |
+
return {"capturable": False, "reason": out}
|
| 714 |
+
return {"capturable": None,
|
| 715 |
+
"reason": "probe child exit %s: %s" % (
|
| 716 |
+
r.returncode, (r.stderr or "")[-200:])}
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
def _bench_verdict(rep):
|
| 720 |
+
parts = ["median %.4g ms" % rep["median_ms"]]
|
| 721 |
+
if "achieved_tflops" in rep:
|
| 722 |
+
parts.append("%.4g TFLOP/s" % rep["achieved_tflops"])
|
| 723 |
+
elif "achieved_gbs" in rep:
|
| 724 |
+
parts.append("%.4g GB/s" % rep["achieved_gbs"])
|
| 725 |
+
if "roofline" in rep:
|
| 726 |
+
parts.append("%.0f%% of %s roof" % (rep["roofline"]["pct_of_roof"],
|
| 727 |
+
rep["roofline"]["bound"]))
|
| 728 |
+
if rep["rejected_throttled"]:
|
| 729 |
+
parts.append("%d/%d rejected (%s)" % (
|
| 730 |
+
rep["rejected_throttled"], rep["samples"],
|
| 731 |
+
rep["rejection_basis"]))
|
| 732 |
+
if rep.get("no_clean_window"):
|
| 733 |
+
parts.append("no clean window: the whole run was throttled")
|
| 734 |
+
if rep.get("alloc_growth_bytes_per_call"):
|
| 735 |
+
parts.append("fn allocates %d B/call" %
|
| 736 |
+
rep["alloc_growth_bytes_per_call"])
|
| 737 |
+
if rep.get("host_overhead_suspected"):
|
| 738 |
+
parts.append("fn appears to sync or do host work inside")
|
| 739 |
+
return "; ".join(parts)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 743 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 744 |
+
graph_probe_isolated=False, device="cuda"):
|
| 745 |
+
"""Median-of-N cudaEvent timing with throttle rejection, roofline placement, and footgun detection."""
|
| 746 |
+
for name, v in (("iters", iters), ("warmup", warmup)):
|
| 747 |
+
if not isinstance(v, int) or v < (1 if name == "iters" else 0):
|
| 748 |
+
raise ValueError(f"{name} must be a non-negative int; "
|
| 749 |
+
f"got {v!r}")
|
| 750 |
+
for name, v in (("bytes", bytes), ("flops", flops)):
|
| 751 |
+
if v is not None and (not isinstance(v, (int, float)) or v <= 0):
|
| 752 |
+
raise ValueError(f"{name} must be a positive number counting "
|
| 753 |
+
f"{name} per call of fn; got {v!r}")
|
|
|
|
| 754 |
for _ in range(warmup):
|
| 755 |
fn()
|
| 756 |
_sync()
|
| 757 |
+
# footgun probes: per-call allocation growth and hidden host work
|
| 758 |
+
alloc0 = torch.cuda.memory_allocated(device)
|
| 759 |
+
fn()
|
| 760 |
+
fn()
|
| 761 |
+
_sync()
|
| 762 |
+
alloc_growth = max(0, (torch.cuda.memory_allocated(device) - alloc0)
|
| 763 |
+
// 2)
|
| 764 |
+
# a burst of enqueues that leaves the stream idle means fn drains it; only trustworthy above the launch-overhead floor
|
| 765 |
+
for _ in range(16):
|
| 766 |
+
fn()
|
| 767 |
+
stream_idle = bool(torch.cuda.current_stream(device).query())
|
| 768 |
+
_sync()
|
| 769 |
+
t0 = time.perf_counter()
|
| 770 |
+
pre = _events(fn, 4)
|
| 771 |
+
wall_ms = (time.perf_counter() - t0) * 1e3
|
| 772 |
+
ev_ms = sum(pre)
|
| 773 |
+
host_suspect = (stream_idle and ev_ms / 4 > 0.05) or \
|
| 774 |
+
wall_ms > ev_ms * 1.5 + 2.0
|
| 775 |
+
|
| 776 |
graph_state = "off"
|
| 777 |
graph_reason = ""
|
| 778 |
+
if graph and graph_probe_isolated:
|
| 779 |
+
cap = probe_capturable(fn)
|
| 780 |
+
if cap["capturable"] is False:
|
| 781 |
+
graph = False
|
| 782 |
+
graph_state = "fallback_eager"
|
| 783 |
+
graph_reason = "isolated probe: " + cap["reason"]
|
| 784 |
if graph:
|
| 785 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 786 |
try:
|
|
|
|
| 795 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 796 |
graph_state = "fallback_eager"
|
| 797 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 798 |
+
# drain the invalidated stream; a failed capture can wedge the device RNG on some torch versions
|
|
|
|
|
|
|
|
|
|
| 799 |
rng_ok = True
|
| 800 |
for _attempt in range(5):
|
| 801 |
try:
|
|
|
|
| 825 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 826 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 827 |
best_clk = max(clocks)
|
| 828 |
+
|
| 829 |
+
# rejection basis: memory-bound work is judged on its own rate, not the SM clock
|
| 830 |
+
ridge_memory = False
|
| 831 |
+
if dossier is not None and bytes and flops:
|
| 832 |
+
bw0 = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 833 |
+
mma_v0 = dossier["mma"]["value"].get(dtype)
|
| 834 |
+
if isinstance(mma_v0, dict):
|
| 835 |
+
mma_v0 = mma_v0.get("dense_equivalent")
|
| 836 |
+
peak0 = (mma_v0 if isinstance(mma_v0, (int, float))
|
| 837 |
+
else dossier["fma_f32"]["value"]) * 1e12
|
| 838 |
+
ridge_memory = (flops / bytes) * bw0 < peak0
|
| 839 |
+
basis = "bandwidth_consistency" if (
|
| 840 |
+
bytes is not None and (flops is None or ridge_memory)) \
|
| 841 |
+
else "sm_clock"
|
| 842 |
+
|
| 843 |
flat = []
|
| 844 |
+
kept = []
|
| 845 |
+
rejected = 0
|
| 846 |
+
if basis == "sm_clock":
|
| 847 |
+
for group, ci in samples:
|
| 848 |
+
window_ok = (clocks[ci] >= clock_guard * best_clk and
|
| 849 |
+
clocks[min(ci + 1, len(clocks) - 1)] >=
|
| 850 |
+
clock_guard * best_clk)
|
| 851 |
+
for ms in group:
|
| 852 |
+
flat.append(ms)
|
| 853 |
+
if window_ok:
|
| 854 |
+
kept.append(ms)
|
| 855 |
+
else:
|
| 856 |
+
rejected += 1
|
| 857 |
+
else:
|
| 858 |
+
for group, _ci in samples:
|
| 859 |
+
flat.extend(group)
|
| 860 |
+
floor = sorted(flat)[max(0, int(0.05 * len(flat)) - 1)]
|
| 861 |
+
for ms in flat:
|
| 862 |
+
if ms <= floor * 1.15:
|
| 863 |
kept.append(ms)
|
| 864 |
else:
|
| 865 |
rejected += 1
|
| 866 |
+
all_rejected = not kept
|
| 867 |
+
if all_rejected:
|
| 868 |
kept = flat
|
| 869 |
med = _median(kept)
|
| 870 |
ks = sorted(kept)
|
| 871 |
n_k = len(ks)
|
| 872 |
+
# order-statistic 95% CI on the median; degenerate at tiny n
|
|
|
|
| 873 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 874 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 875 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
|
|
| 884 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 885 |
"samples": len(flat),
|
| 886 |
"rejected_throttled": rejected,
|
| 887 |
+
"rejection_basis": basis,
|
| 888 |
"graph_captured": graph_state == "captured",
|
| 889 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 890 |
"worst": round(min(clocks), 3)},
|
| 891 |
}
|
| 892 |
+
no_clean = all_rejected
|
| 893 |
+
if basis == "sm_clock" and dossier is not None:
|
| 894 |
+
ref_clk = dossier["clock"]["max_ghz"]
|
| 895 |
+
if best_clk < clock_guard * ref_clk:
|
| 896 |
+
no_clean = True
|
| 897 |
+
if no_clean:
|
| 898 |
+
rep["no_clean_window"] = True
|
| 899 |
if graph_reason:
|
| 900 |
rep["graph_fallback_reason"] = graph_reason
|
| 901 |
+
if alloc_growth:
|
| 902 |
+
rep["alloc_growth_bytes_per_call"] = int(alloc_growth)
|
| 903 |
+
if host_suspect:
|
| 904 |
+
rep["host_overhead_suspected"] = True
|
| 905 |
+
rep["host_wall_over_event"] = round(wall_ms / max(ev_ms, 1e-9), 2)
|
| 906 |
if bytes is not None:
|
| 907 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 908 |
if flops is not None:
|
|
|
|
| 912 |
if dossier is not None and bytes and flops:
|
| 913 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 914 |
mma_v = dossier["mma"]["value"].get(dtype)
|
| 915 |
+
if isinstance(mma_v, dict):
|
| 916 |
+
mma_v = mma_v.get("dense_equivalent")
|
| 917 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 918 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 919 |
ai = flops / bytes
|
|
|
|
| 924 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 925 |
"peak_dtype": dtype,
|
| 926 |
}
|
| 927 |
+
rep["verdict"] = _bench_verdict(rep)
|
| 928 |
return rep
|
| 929 |
|
| 930 |
|
| 931 |
def _to_ordered_int(t):
|
| 932 |
+
"""Monotone integer mapping of float bit patterns; differences are ULP distances."""
|
|
|
|
|
|
|
| 933 |
if t.dtype == torch.float32:
|
| 934 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 935 |
half = 1 << 31
|
|
|
|
| 945 |
|
| 946 |
|
| 947 |
def ulp_diff(a, b):
|
| 948 |
+
"""Elementwise ULP distance; both-NaN counts 0, one-sided NaN 2**62, integers by absolute difference."""
|
| 949 |
+
if a.dtype != b.dtype or a.shape != b.shape:
|
| 950 |
+
raise ValueError("ulp_diff needs matching dtype and shape; got "
|
| 951 |
+
f"{a.dtype}{tuple(a.shape)} vs "
|
| 952 |
+
f"{b.dtype}{tuple(b.shape)}; cast or reshape the "
|
| 953 |
+
"candidate to the reference layout")
|
| 954 |
if not a.is_floating_point():
|
| 955 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 956 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
|
|
| 967 |
|
| 968 |
@staticmethod
|
| 969 |
def last_dim():
|
| 970 |
+
"""Identical permutation of every arg's last dim: reduction order changes, value does not."""
|
|
|
|
|
|
|
| 971 |
def shuffle(args, seed):
|
| 972 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 973 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
|
|
| 977 |
|
| 978 |
@staticmethod
|
| 979 |
def matmul_k():
|
| 980 |
+
"""Consistent K permutation of (A [.., M, K], B [.., K, N]): product unchanged, accumulation order not."""
|
|
|
|
| 981 |
def shuffle(args, seed):
|
| 982 |
a_m, b_m = args[0], args[1]
|
| 983 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
|
|
| 991 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 992 |
|
| 993 |
|
| 994 |
+
def _compare_hints(trial):
|
| 995 |
+
hints = []
|
| 996 |
+
if trial.get("bias_one_sided_frac", 0) >= 0.9 and \
|
| 997 |
+
trial.get("frac_above_band", 0) > 0.5:
|
| 998 |
+
hints.append("systematic one-sided bias: check scale factors or "
|
| 999 |
+
"the accumulator dtype")
|
| 1000 |
+
if trial["nan_cand"] > trial["nan_ref"]:
|
| 1001 |
+
hints.append("candidate introduces NaN: check uninitialized "
|
| 1002 |
+
"memory or masking")
|
| 1003 |
+
nd = trial.get("first_divergent_nd")
|
| 1004 |
+
if nd and nd[-1] > 0 and any(nd[-1] % t == 0 for t in (16, 32, 64,
|
| 1005 |
+
128)):
|
| 1006 |
+
hints.append("divergence starts on a tile-aligned index: check "
|
| 1007 |
+
"boundary indexing")
|
| 1008 |
+
return hints
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def compare(ref, cand, gen=None, trials=8, order_shuffle=None,
|
| 1012 |
+
order_trials=8, ref_variants=None, ulp_tol=None, atol=None,
|
| 1013 |
+
rtol=None, band_margin=4.0, zero_atol=None, worst_k=8, seed=0,
|
| 1014 |
+
inputs=None, frac_above_tol=0.5):
|
| 1015 |
+
"""Adversarial correctness comparison with pointwise order bands, ULP evidence, and a one-sided-bias gate; the banded route cannot reject errors under band_margin*(band+2) ULP at order-stable elements."""
|
| 1016 |
+
if (gen is None) == (inputs is None):
|
| 1017 |
+
raise ValueError("pass exactly one of gen (a seed -> args "
|
| 1018 |
+
"callable) or inputs (a tuple of concrete "
|
| 1019 |
+
"tensors)")
|
| 1020 |
+
if inputs is not None:
|
| 1021 |
+
if isinstance(inputs, torch.Tensor):
|
| 1022 |
+
inputs = (inputs,)
|
| 1023 |
+
if not all(isinstance(x, torch.Tensor) for x in inputs):
|
| 1024 |
+
raise ValueError("inputs must be tensors; wrap scalars with "
|
| 1025 |
+
"torch.tensor first")
|
| 1026 |
+
fixed = tuple(inputs)
|
| 1027 |
+
gen = lambda seed: fixed # noqa: E731
|
| 1028 |
+
trials = 1
|
| 1029 |
worst = {"max_ulp": -1}
|
| 1030 |
total_hist = {}
|
| 1031 |
per_trial = []
|
| 1032 |
max_abs_seen = 0.0
|
| 1033 |
max_rel_seen = 0.0
|
| 1034 |
+
out_shapes = []
|
| 1035 |
for t in range(trials):
|
| 1036 |
args = gen(seed + t)
|
| 1037 |
+
if not isinstance(args, (tuple, list)):
|
| 1038 |
+
raise ValueError("gen(seed) must return a tuple of args; got "
|
| 1039 |
+
f"{type(args).__name__}; return (x,) for a "
|
| 1040 |
+
"single-argument op")
|
| 1041 |
r_outs = _as_outputs(ref(*args))
|
| 1042 |
c_outs = _as_outputs(cand(*args))
|
| 1043 |
+
if len(r_outs) != len(c_outs):
|
| 1044 |
+
raise ValueError(f"reference returned {len(r_outs)} outputs, "
|
| 1045 |
+
f"candidate {len(c_outs)}; make the arities "
|
| 1046 |
+
"match")
|
| 1047 |
+
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
| 1048 |
+
if r.dtype != c.dtype:
|
| 1049 |
+
raise ValueError(f"output {oi} dtype mismatch: ref "
|
| 1050 |
+
f"{r.dtype}, cand {c.dtype}; cast the "
|
| 1051 |
+
"candidate to the reference dtype")
|
| 1052 |
+
if r.shape != c.shape:
|
| 1053 |
+
raise ValueError(f"output {oi} shape mismatch: ref "
|
| 1054 |
+
f"{tuple(r.shape)}, cand "
|
| 1055 |
+
f"{tuple(c.shape)}")
|
| 1056 |
+
out_shapes = [tuple(r.shape) for r in r_outs]
|
| 1057 |
+
# pointwise band: each element judged against its own measured order sensitivity
|
| 1058 |
bands = None
|
| 1059 |
band = 0
|
| 1060 |
band_score = 0.0
|
|
|
|
| 1078 |
max_ulp = 0
|
| 1079 |
mean_ulp = 0.0
|
| 1080 |
first_div = -1
|
| 1081 |
+
first_div_nd = None
|
| 1082 |
n_el = 0
|
| 1083 |
n_above_band = 0
|
| 1084 |
+
n_diverging = 0
|
| 1085 |
+
n_cand_high = 0
|
| 1086 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 1087 |
worst_elements = []
|
| 1088 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
|
|
| 1100 |
mean_ulp += float(d.double().sum().item())
|
| 1101 |
n_el += d.numel()
|
| 1102 |
nz = (d > 0)
|
| 1103 |
+
n_diverging += int(nz.sum().item())
|
| 1104 |
+
n_cand_high += int(((c > r) & nz).sum().item())
|
| 1105 |
if first_div < 0 and nz.any():
|
| 1106 |
first_div = int(nz.flatten().nonzero()[0].item())
|
| 1107 |
+
first_div_nd = _unravel(first_div, r.shape)
|
| 1108 |
if nz.any():
|
| 1109 |
buckets = torch.clamp(
|
| 1110 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
|
|
| 1121 |
if val > 0:
|
| 1122 |
worst_elements.append({
|
| 1123 |
"output": oi, "index": int(ix),
|
| 1124 |
+
"index_nd": _unravel(int(ix), r.shape),
|
| 1125 |
"ref": float(rf[ix].item()),
|
| 1126 |
"cand": float(cf[ix].item()),
|
| 1127 |
"ulp": int(val)})
|
|
|
|
| 1141 |
inf_r += int(torch.isinf(r).sum().item())
|
| 1142 |
inf_c += int(torch.isinf(c).sum().item())
|
| 1143 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
| 1144 |
+
frac_pos = n_cand_high / max(n_diverging, 1)
|
| 1145 |
trial_rep = {
|
| 1146 |
"max_ulp": max_ulp,
|
| 1147 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 1148 |
"first_divergent_index": first_div,
|
| 1149 |
+
"first_divergent_nd": first_div_nd,
|
| 1150 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 1151 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 1152 |
"order_band_ulp": band,
|
| 1153 |
"band_score": round(band_score, 2),
|
| 1154 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
| 1155 |
+
"bias_one_sided_frac": round(max(frac_pos, 1 - frac_pos), 4)
|
| 1156 |
+
if n_diverging else 0.0,
|
| 1157 |
"worst_elements": worst_elements[:worst_k],
|
| 1158 |
}
|
| 1159 |
per_trial.append(trial_rep)
|
| 1160 |
if max_ulp > worst["max_ulp"]:
|
| 1161 |
worst = trial_rep
|
|
|
|
| 1162 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 1163 |
+
banded = order_shuffle is not None or ref_variants
|
| 1164 |
+
if banded:
|
| 1165 |
score = max(t["band_score"] for t in per_trial)
|
| 1166 |
ulp_ok = score <= band_margin
|
| 1167 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
|
|
| 1176 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 1177 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 1178 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 1179 |
+
# one-sided bias gate: a uniform small error is above-band nearly everywhere in one direction
|
| 1180 |
+
bias_fail = False
|
| 1181 |
+
if banded and (ulp_ok or tol_ok):
|
| 1182 |
+
w_frac = max(t["frac_above_band"] for t in per_trial)
|
| 1183 |
+
w_side = max(t["bias_one_sided_frac"] for t in per_trial)
|
| 1184 |
+
if w_frac > frac_above_tol and w_side >= 0.9:
|
| 1185 |
+
bias_fail = True
|
| 1186 |
+
criterion += ("; FAILED bias gate: frac_above_band %.3f > %s "
|
| 1187 |
+
"with one-sided fraction %.2f" %
|
| 1188 |
+
(w_frac, frac_above_tol, w_side))
|
| 1189 |
+
passed = bool((ulp_ok or tol_ok) and not bias_fail)
|
| 1190 |
+
hints = [] if passed else _compare_hints(worst)
|
| 1191 |
+
if passed:
|
| 1192 |
+
verdict = "pass: " + (
|
| 1193 |
+
f"max {max_seen} ULP within the order band" if banded
|
| 1194 |
+
else f"max {max_seen} ULP")
|
| 1195 |
+
elif bias_fail:
|
| 1196 |
+
verdict = ("fail: uniform bias, %.0f%% of elements above their "
|
| 1197 |
+
"order band in one direction" %
|
| 1198 |
+
(100 * max(t["frac_above_band"] for t in per_trial)))
|
| 1199 |
+
else:
|
| 1200 |
+
w0 = worst["worst_elements"][0] if worst["worst_elements"] else None
|
| 1201 |
+
verdict = "fail: max %d ULP" % max_seen + (
|
| 1202 |
+
" at index %s (ref %.6g, cand %.6g)" %
|
| 1203 |
+
(w0["index_nd"], w0["ref"], w0["cand"]) if w0 else "")
|
| 1204 |
+
rep = {
|
| 1205 |
+
"pass": passed,
|
| 1206 |
+
"verdict": verdict,
|
| 1207 |
"criterion": criterion,
|
| 1208 |
"worst_trial": worst,
|
| 1209 |
"max_abs": max_abs_seen,
|
|
|
|
| 1211 |
"trials": per_trial,
|
| 1212 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 1213 |
}
|
| 1214 |
+
if hints:
|
| 1215 |
+
rep["hints"] = hints
|
| 1216 |
+
return rep
|
| 1217 |
|
| 1218 |
|
| 1219 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
|
|
| 1229 |
return False
|
| 1230 |
|
| 1231 |
|
| 1232 |
+
class _RandnMaker:
|
| 1233 |
+
"""Picklable default maker: `arity` same-shape randn tensors (zeros for empty shapes)."""
|
| 1234 |
+
|
| 1235 |
+
def __init__(self, arity=1):
|
| 1236 |
+
self.arity = arity
|
| 1237 |
+
|
| 1238 |
+
def __call__(self, shape, dtype=torch.float32):
|
| 1239 |
+
n = math.prod(shape)
|
| 1240 |
+
if n:
|
| 1241 |
+
out = tuple(torch.randn(tuple(shape), device="cuda",
|
| 1242 |
+
dtype=dtype)
|
| 1243 |
+
for _ in range(self.arity))
|
| 1244 |
+
else:
|
| 1245 |
+
out = tuple(torch.zeros(tuple(shape), device="cuda",
|
| 1246 |
+
dtype=dtype)
|
| 1247 |
+
for _ in range(self.arity))
|
| 1248 |
+
return out if self.arity > 1 else out[0]
|
| 1249 |
+
|
| 1250 |
+
|
| 1251 |
+
def _default_make(arity=1):
|
| 1252 |
+
"""Default maker instance; a class so isolate=True can transport it."""
|
| 1253 |
+
return _RandnMaker(arity)
|
| 1254 |
+
|
| 1255 |
+
|
| 1256 |
def _fuzz_build(make, shape, dtype):
|
| 1257 |
try:
|
| 1258 |
out = make(tuple(shape), dtype)
|
|
|
|
| 1269 |
if v == "strided_rows":
|
| 1270 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 1271 |
if v == "offset_slice":
|
| 1272 |
+
# storage-offset-1 view: misaligned for vectorized loads
|
| 1273 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 1274 |
if v == "overflow_expand":
|
| 1275 |
if x.numel() == 0:
|
|
|
|
| 1283 |
|
| 1284 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 1285 |
stop_on_context_loss=True):
|
| 1286 |
+
"""All variant cases for one (shape, dtype) group; returns (rows, context_lost)."""
|
|
|
|
| 1287 |
rows = []
|
| 1288 |
bases = _fuzz_build(make, shape, dtype)
|
| 1289 |
lost = False
|
|
|
|
| 1320 |
got = op(*xs)
|
| 1321 |
want = ref(*xs)
|
| 1322 |
if v == "overflow_expand":
|
| 1323 |
+
# bounded comparison: head plus the high-index tail where int32 indexing wraps
|
|
|
|
| 1324 |
w = 1 << 20
|
| 1325 |
gf, wf = got.flatten(), want.flatten()
|
| 1326 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
|
|
| 1345 |
return rows, lost
|
| 1346 |
|
| 1347 |
|
| 1348 |
+
def _fn_spec(fn, name):
|
| 1349 |
+
"""Picklable transport spec for a callable, with a source fallback for local defs."""
|
| 1350 |
+
import pickle
|
| 1351 |
+
try:
|
| 1352 |
+
return ("pickle", pickle.dumps(fn), None)
|
| 1353 |
+
except Exception: # noqa: BLE001
|
| 1354 |
+
import inspect
|
| 1355 |
+
import textwrap
|
| 1356 |
+
try:
|
| 1357 |
+
src = textwrap.dedent(inspect.getsource(fn))
|
| 1358 |
+
except (OSError, TypeError):
|
| 1359 |
+
src = ""
|
| 1360 |
+
if not src.startswith("def "):
|
| 1361 |
+
raise ValueError(
|
| 1362 |
+
f"{name} is not picklable and not a plain def; define "
|
| 1363 |
+
"op/ref/make as top-level functions (not lambdas or "
|
| 1364 |
+
"closures) to use isolate=True") from None
|
| 1365 |
+
return ("source", src, fn.__name__)
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
_POOL_BOOTSTRAP = r"""
|
| 1369 |
+
import json, os, pickle, sys
|
| 1370 |
meta = json.load(open(sys.argv[1], "r"))
|
| 1371 |
+
sys.path[:0] = meta["sys_path"]
|
| 1372 |
try:
|
| 1373 |
import kernels
|
| 1374 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
|
|
| 1381 |
import load_local
|
| 1382 |
_mk = load_local.load()
|
| 1383 |
import torch
|
| 1384 |
+
_ns = {"torch": torch}
|
| 1385 |
+
def _resolve(spec):
|
| 1386 |
+
kind, payload, name = spec
|
| 1387 |
+
if kind == "pickle":
|
| 1388 |
+
return pickle.loads(payload)
|
| 1389 |
+
exec(payload, _ns)
|
| 1390 |
+
return _ns[name]
|
| 1391 |
+
_op, _ref, _make = (_resolve(s) for s in
|
| 1392 |
+
pickle.load(open(meta["fns"], "rb")))
|
| 1393 |
+
print("ready", flush=True)
|
| 1394 |
+
for line in sys.stdin:
|
| 1395 |
+
job = json.loads(line)
|
| 1396 |
+
rows, _l = _mk._fuzz_group(
|
| 1397 |
+
_op, _ref, _make, tuple(job["shape"]),
|
| 1398 |
+
getattr(torch, job["dtype"]), tuple(job["variants"]),
|
| 1399 |
+
job["ulp_tol"], job["device"], stop_on_context_loss=False)
|
| 1400 |
+
pickle.dump(rows, open(job["out"], "wb"))
|
| 1401 |
+
print("done", flush=True)
|
| 1402 |
"""
|
| 1403 |
|
| 1404 |
|
| 1405 |
+
class _FuzzPool:
|
| 1406 |
+
"""One persistent isolation worker fed groups over stdin, respawned only after a crash."""
|
| 1407 |
+
|
| 1408 |
+
def __init__(self, meta_path, stderr_path, timeout_s):
|
| 1409 |
+
self.meta_path = meta_path
|
| 1410 |
+
self.stderr_path = stderr_path
|
| 1411 |
+
self.timeout_s = timeout_s
|
| 1412 |
+
self.proc = None
|
| 1413 |
+
self.q = None
|
| 1414 |
+
|
| 1415 |
+
def _spawn(self):
|
| 1416 |
+
import queue
|
| 1417 |
+
import subprocess
|
| 1418 |
+
import sys as _sys
|
| 1419 |
+
import threading
|
| 1420 |
+
self.errf = open(self.stderr_path, "ab")
|
| 1421 |
+
self.proc = subprocess.Popen(
|
| 1422 |
+
[_sys.executable, "-c", _POOL_BOOTSTRAP, self.meta_path],
|
| 1423 |
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
| 1424 |
+
stderr=self.errf, text=True)
|
| 1425 |
+
self.q = queue.Queue()
|
| 1426 |
+
|
| 1427 |
+
def reader(p, q):
|
| 1428 |
+
for line in p.stdout:
|
| 1429 |
+
q.put(line.strip())
|
| 1430 |
+
threading.Thread(target=reader, args=(self.proc, self.q),
|
| 1431 |
+
daemon=True).start()
|
| 1432 |
+
try:
|
| 1433 |
+
first = self.q.get(timeout=self.timeout_s)
|
| 1434 |
+
except Exception: # noqa: BLE001
|
| 1435 |
+
first = None
|
| 1436 |
+
return first == "ready"
|
| 1437 |
+
|
| 1438 |
+
def run(self, job_line):
|
| 1439 |
+
"""Returns True when the worker reports done, False on crash or timeout."""
|
| 1440 |
+
import queue
|
| 1441 |
+
if self.proc is None or self.proc.poll() is not None:
|
| 1442 |
+
if not self._spawn():
|
| 1443 |
+
return False
|
| 1444 |
+
try:
|
| 1445 |
+
self.proc.stdin.write(job_line + "\n")
|
| 1446 |
+
self.proc.stdin.flush()
|
| 1447 |
+
except OSError:
|
| 1448 |
+
return False
|
| 1449 |
+
try:
|
| 1450 |
+
return self.q.get(timeout=self.timeout_s) == "done"
|
| 1451 |
+
except queue.Empty:
|
| 1452 |
+
if self.proc.poll() is None:
|
| 1453 |
+
self.proc.kill()
|
| 1454 |
+
return False
|
| 1455 |
+
|
| 1456 |
+
def stderr_tail(self):
|
| 1457 |
+
try:
|
| 1458 |
+
self.errf.flush()
|
| 1459 |
+
with open(self.stderr_path, "rb") as f:
|
| 1460 |
+
return f.read()[-400:].decode(errors="replace")
|
| 1461 |
+
except OSError:
|
| 1462 |
+
return ""
|
| 1463 |
+
|
| 1464 |
+
def close(self):
|
| 1465 |
+
if self.proc is not None and self.proc.poll() is None:
|
| 1466 |
+
self.proc.kill()
|
| 1467 |
+
try:
|
| 1468 |
+
self.errf.close()
|
| 1469 |
+
except Exception: # noqa: BLE001
|
| 1470 |
+
pass
|
| 1471 |
+
|
| 1472 |
+
|
| 1473 |
+
def _fuzz_hints(rows):
|
| 1474 |
+
hints = []
|
| 1475 |
+
bad = [c for c in rows if c["status"] == "mismatch"]
|
| 1476 |
+
if bad and all(c["variant"] in ("transposed", "strided_rows",
|
| 1477 |
+
"offset_slice") for c in bad):
|
| 1478 |
+
hints.append("mismatches only on non-contiguous variants: the op "
|
| 1479 |
+
"assumes contiguity; call .contiguous() or honor "
|
| 1480 |
+
"strides")
|
| 1481 |
+
if any(c["variant"] == "overflow_expand" and
|
| 1482 |
+
c["status"] == "mismatch" for c in rows):
|
| 1483 |
+
hints.append("overflow_expand mismatch: int32 indexing wraps past "
|
| 1484 |
+
"2^31 elements; use 64-bit index arithmetic")
|
| 1485 |
+
return hints
|
| 1486 |
+
|
| 1487 |
+
|
| 1488 |
+
def fuzz_shapes(op, ref, make=None, shapes=None, variants=_FUZZ_VARIANTS,
|
| 1489 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 1490 |
+
isolate_timeout_s=180, device="cuda", arity=1):
|
| 1491 |
+
"""Edge shapes, dtypes, and layout mutations with per-case repro recipes; isolate=True survives context-killing candidates via a pooled worker."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1492 |
if shapes is None:
|
| 1493 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1494 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
| 1495 |
+
if make is None:
|
| 1496 |
+
make = _default_make(arity)
|
| 1497 |
|
| 1498 |
rows = []
|
| 1499 |
lost = False
|
| 1500 |
if isolate:
|
| 1501 |
import json
|
| 1502 |
import pickle
|
|
|
|
| 1503 |
import sys as _sys
|
| 1504 |
import tempfile
|
| 1505 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1506 |
fns = os.path.join(tmpdir, "fns.pkl")
|
| 1507 |
+
specs = [_fn_spec(f, n) for f, n in ((op, "op"), (ref, "ref"),
|
| 1508 |
+
(make, "make"))]
|
| 1509 |
with open(fns, "wb") as f:
|
| 1510 |
+
pickle.dump(specs, f)
|
| 1511 |
+
meta_path = os.path.join(tmpdir, "meta.json")
|
| 1512 |
+
with open(meta_path, "w", encoding="utf-8") as f:
|
| 1513 |
+
json.dump({"sys_path": [p for p in _sys.path
|
| 1514 |
+
if isinstance(p, str)],
|
| 1515 |
+
"repo": _REPO_ID, "version": _REPO_VERSION,
|
| 1516 |
+
"fns": fns}, f)
|
| 1517 |
+
pool = _FuzzPool(meta_path, os.path.join(tmpdir, "stderr.log"),
|
| 1518 |
+
isolate_timeout_s)
|
| 1519 |
+
try:
|
| 1520 |
+
for dtype in dtypes:
|
| 1521 |
+
for shape in shapes:
|
| 1522 |
+
out = os.path.join(tmpdir, "rows.pkl")
|
| 1523 |
+
if os.path.exists(out):
|
| 1524 |
+
os.remove(out)
|
| 1525 |
+
job = json.dumps({
|
| 1526 |
"shape": list(shape),
|
| 1527 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1528 |
"variants": list(variants),
|
| 1529 |
"ulp_tol": ulp_tol, "device": device,
|
| 1530 |
+
"out": out})
|
| 1531 |
+
ok = pool.run(job)
|
| 1532 |
+
if ok and os.path.exists(out):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
with open(out, "rb") as f:
|
| 1534 |
+
rows.extend(pickle.load(f))
|
| 1535 |
+
else:
|
| 1536 |
+
rows.append({
|
| 1537 |
+
"shape": tuple(shape), "variant": "*",
|
| 1538 |
+
"dtype": str(dtype).replace("torch.", ""),
|
| 1539 |
+
"status": "crashed_isolated",
|
| 1540 |
+
"stderr_tail": pool.stderr_tail()})
|
| 1541 |
+
finally:
|
| 1542 |
+
pool.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1543 |
else:
|
| 1544 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1545 |
for gi, (dtype, shape) in enumerate(pending):
|
|
|
|
| 1556 |
n_bad = sum(1 for r in rows
|
| 1557 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1558 |
"skipped_empty"))
|
| 1559 |
+
rep = {"cases": rows, "failed": n_bad, "total": len(rows),
|
| 1560 |
+
"context_lost": lost}
|
| 1561 |
+
hints = _fuzz_hints(rows)
|
| 1562 |
+
if hints:
|
| 1563 |
+
rep["hints"] = hints
|
| 1564 |
+
if n_bad == 0:
|
| 1565 |
+
rep["verdict"] = "pass: %d/%d cases ok" % (len(rows) - n_bad,
|
| 1566 |
+
len(rows))
|
| 1567 |
+
else:
|
| 1568 |
+
worst_case = next(c for c in rows
|
| 1569 |
+
if c["status"] not in ("ok", "skipped_vram",
|
| 1570 |
+
"skipped_empty"))
|
| 1571 |
+
rep["verdict"] = "fail: %d/%d cases (%s at %s %s)" % (
|
| 1572 |
+
n_bad, len(rows), worst_case["status"], worst_case["shape"],
|
| 1573 |
+
worst_case["variant"])
|
| 1574 |
+
return rep
|
| 1575 |
|
| 1576 |
|
| 1577 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1578 |
check=None, budget_s=None, race=False):
|
| 1579 |
+
"""Launch-configuration search, fastest first, with a correctness gate per row and no silently dropped points."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1580 |
keys = list(grid.keys())
|
| 1581 |
|
| 1582 |
def expand(i, acc):
|
|
|
|
| 1641 |
continue
|
| 1642 |
rows.append(run_point(p, iters))
|
| 1643 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
| 1644 |
+
if rows:
|
| 1645 |
+
n_pruned = sum(1 for r in rows if r.get("status") == "pruned_race")
|
| 1646 |
+
n_skip = sum(1 for r in rows if r.get("status") == "not_run")
|
| 1647 |
+
best = rows[0]
|
| 1648 |
+
rows_verdict = "fastest %s at %.4g ms" % (
|
| 1649 |
+
{k: best[k] for k in keys}, best["median_ms"])
|
| 1650 |
+
if n_pruned:
|
| 1651 |
+
rows_verdict += "; %d pruned" % n_pruned
|
| 1652 |
+
if n_skip:
|
| 1653 |
+
rows_verdict += "; %d not run" % n_skip
|
| 1654 |
+
best["verdict"] = rows_verdict
|
| 1655 |
return rows
|
| 1656 |
|
| 1657 |
|
| 1658 |
+
def judge(cand, ref, inputs=None, gen=None, bytes=None, flops=None,
|
| 1659 |
+
dtype=None, order_shuffle=None, make=None, quick_fuzz=True,
|
| 1660 |
+
device="cuda"):
|
| 1661 |
+
"""One-call verdict: compare, a quick fuzz, and a roofline bench against the cached dossier."""
|
| 1662 |
+
if (gen is None) == (inputs is None):
|
| 1663 |
+
raise ValueError("pass exactly one of inputs (concrete tensors) "
|
| 1664 |
+
"or gen (seed -> args)")
|
| 1665 |
+
if inputs is not None and isinstance(inputs, torch.Tensor):
|
| 1666 |
+
inputs = (inputs,)
|
| 1667 |
+
args0 = tuple(inputs) if inputs is not None else gen(0)
|
| 1668 |
+
arity = len(args0)
|
| 1669 |
+
if dtype is None:
|
| 1670 |
+
dt0 = args0[0].dtype
|
| 1671 |
+
dtype = {torch.float16: "fp16", torch.bfloat16: "bf16"}.get(
|
| 1672 |
+
dt0, "f32")
|
| 1673 |
+
dossier = probe_device(device, quick=True, cached=True,
|
| 1674 |
+
tenancy_check=False)
|
| 1675 |
+
cmp_rep = compare(ref, cand, gen=gen, inputs=inputs, trials=4,
|
| 1676 |
+
order_shuffle=order_shuffle)
|
| 1677 |
+
fuzz_rep = None
|
| 1678 |
+
if quick_fuzz:
|
| 1679 |
+
fuzz_rep = fuzz_shapes(
|
| 1680 |
+
cand, ref, make=make, arity=arity,
|
| 1681 |
+
shapes=[(1, 17), (3, 63), (128, 128)],
|
| 1682 |
+
variants=("contig", "transposed", "offset_slice"),
|
| 1683 |
+
dtypes=(args0[0].dtype,))
|
| 1684 |
+
bench_rep = None
|
| 1685 |
+
if inputs is not None:
|
| 1686 |
+
bench_rep = bench(lambda: cand(*inputs), iters=32, warmup=6,
|
| 1687 |
+
bytes=bytes, flops=flops, dossier=dossier,
|
| 1688 |
+
dtype=dtype, device=device)
|
| 1689 |
+
passed = cmp_rep["pass"] and (fuzz_rep is None or
|
| 1690 |
+
fuzz_rep["failed"] == 0)
|
| 1691 |
+
parts = [cmp_rep["verdict"]]
|
| 1692 |
+
if fuzz_rep is not None:
|
| 1693 |
+
parts.append(fuzz_rep["verdict"])
|
| 1694 |
+
if bench_rep is not None:
|
| 1695 |
+
parts.append(bench_rep["verdict"])
|
| 1696 |
+
rep = {"pass": passed, "verdict" : " | ".join(parts),
|
| 1697 |
+
"compare": cmp_rep, "fuzz": fuzz_rep, "bench": bench_rep,
|
| 1698 |
+
"device": dossier["device"]["name"]}
|
| 1699 |
+
hints = list(cmp_rep.get("hints", []))
|
| 1700 |
+
if fuzz_rep is not None:
|
| 1701 |
+
hints += fuzz_rep.get("hints", [])
|
| 1702 |
+
if hints:
|
| 1703 |
+
rep["hints"] = hints
|
| 1704 |
+
return rep
|
| 1705 |
+
|
| 1706 |
+
|
| 1707 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1708 |
+
"""Device buffer for MK_STAMP (1-D) or MK_STAMP_BLOCK ([blocks, n]) stamps."""
|
|
|
|
|
|
|
| 1709 |
if blocks is not None:
|
| 1710 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1711 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
|
|
| 1715 |
|
| 1716 |
|
| 1717 |
def pinned_dossiers():
|
| 1718 |
+
"""Names of the release-pinned dossiers, one per certified architecture."""
|
|
|
|
| 1719 |
return sorted(_PINNED.keys())
|
| 1720 |
|
| 1721 |
|
|
|
|
| 1734 |
|
| 1735 |
|
| 1736 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1737 |
+
"""Per-leaf relative drift of a fresh dossier against the release pin for the same architecture."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1738 |
if pinned is None:
|
| 1739 |
arch = current["device"]["sm_arch"]
|
| 1740 |
matches = [k for k, v in _PINNED.items()
|
|
|
|
| 1757 |
"rel": round(rel, 4)})
|
| 1758 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1759 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1760 |
+
rep = {"entries": entries, "worst_rel": worst, "over_tol": over,
|
| 1761 |
+
"rel_tol": rel_tol, "compared": len(entries),
|
| 1762 |
+
"pass": len(over) == 0}
|
| 1763 |
+
rep["verdict"] = ("pass: worst drift %.3f over %d entries" %
|
| 1764 |
+
(worst, len(entries))) if rep["pass"] else \
|
| 1765 |
+
("fail: %d entries over %.2f, worst %s at %.3f" %
|
| 1766 |
+
(len(over), rel_tol, over[0]["path"], worst))
|
| 1767 |
+
return rep
|
| 1768 |
|
| 1769 |
|
| 1770 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1771 |
device="cuda"):
|
| 1772 |
+
"""Run the shipped MK_STAMP_BLOCK example (load, compute, drained store) and decode its stamps."""
|
|
|
|
|
|
|
| 1773 |
props = torch.cuda.get_device_properties(device)
|
| 1774 |
if blocks is None:
|
| 1775 |
blocks = props.multi_processor_count
|
|
|
|
| 1782 |
|
| 1783 |
|
| 1784 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1785 |
+
"""Decode MK_STAMP deltas to ticks and microseconds; 2-D buffers yield min/median/max across blocks."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1786 |
if clock_ghz is None:
|
| 1787 |
clock_ghz = measure_clock(stamps.device)
|
| 1788 |
|
|
|
|
| 1818 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1819 |
"blocks_reporting": int(live.sum().item()),
|
| 1820 |
"blocks_total": int(live.numel())}
|
| 1821 |
+
|
| 1822 |
+
|
| 1823 |
+
_SCHEMAS = {
|
| 1824 |
+
"probe_device": {
|
| 1825 |
+
"device": "name, sm_arch, sms, l2_bytes, vram_bytes, torch, "
|
| 1826 |
+
"cuda_runtime, ecc, driver, power_limit_w",
|
| 1827 |
+
"clock": "max/min/final GHz under sustained FMA load plus sag_pct",
|
| 1828 |
+
"dram_bw": "GB/s: triad_4B/8B/16B, read_only, write_only, "
|
| 1829 |
+
"random_gather_16B",
|
| 1830 |
+
"l2_bw": "GB/s over an L2-resident working set",
|
| 1831 |
+
"latency": "cycles: smem, l1, l2, dram (min of 3 chases) plus "
|
| 1832 |
+
"ns_per_cycle",
|
| 1833 |
+
"fma_f32 / fma_f64": "TFLOP/s of the register-resident pipes",
|
| 1834 |
+
"fma_ilp": "TFLOP/s at 1/2/4 chains plus ratio_4_1; near 4 means "
|
| 1835 |
+
"latency-bound",
|
| 1836 |
+
"mma": "TFLOP/s per dtype; sparse carries executed and "
|
| 1837 |
+
"dense_equivalent; verified_k_lanes carries "
|
| 1838 |
+
"executed_k_per_iter, vs_nominal, calibration_linear; "
|
| 1839 |
+
"fp8_accum carries the accumulate-precision stall",
|
| 1840 |
+
"mma_fed": "TFLOP/s of smem-staged fp16 tiles (staging exposed)",
|
| 1841 |
+
"smem_bw": "GB/s aggregate, conflict-light pattern",
|
| 1842 |
+
"smem_conflict_penalty": "time ratio vs stride 1 per lane stride",
|
| 1843 |
+
"transfer_bw": "GB/s: h2d/d2h pinned and pageable, d2d",
|
| 1844 |
+
"atomics_f32": "Gop/s curve over distinct addresses plus "
|
| 1845 |
+
"shared_same_addr",
|
| 1846 |
+
"launch_overhead": "us: torch_queued, torch_round_trip, "
|
| 1847 |
+
"raw_queued, raw_round_trip",
|
| 1848 |
+
"grid_barrier": "us per grid.sync or 'unsupported'",
|
| 1849 |
+
"occupancy_blocks_per_sm": "API table by (threads, dyn smem)",
|
| 1850 |
+
"occupancy_knees_fma_tflops": "timed dependent-chain table",
|
| 1851 |
+
},
|
| 1852 |
+
"bench": {
|
| 1853 |
+
"median_ms": "median over accepted samples",
|
| 1854 |
+
"sustained_ms": "median over all samples",
|
| 1855 |
+
"median_ci95_ms / p05_ms / p95_ms / min_ms / mean_ms":
|
| 1856 |
+
"distribution of accepted samples",
|
| 1857 |
+
"samples / rejected_throttled / rejection_basis":
|
| 1858 |
+
"counts and whether rejection used sm_clock or "
|
| 1859 |
+
"bandwidth_consistency",
|
| 1860 |
+
"no_clean_window": "present when every window ran throttled; the "
|
| 1861 |
+
"median is then a throttled-regime number",
|
| 1862 |
+
"clock_ghz": "best and worst SM clock probes around the run",
|
| 1863 |
+
"achieved_gbs / achieved_tflops / arithmetic_intensity":
|
| 1864 |
+
"rates from declared traffic",
|
| 1865 |
+
"roofline": "bound, ridge_flops_per_byte, pct_of_roof, peak_dtype",
|
| 1866 |
+
"graph_captured / graph_fallback_reason": "CUDA-graph state",
|
| 1867 |
+
"alloc_growth_bytes_per_call": "present when fn leaks allocations",
|
| 1868 |
+
"host_overhead_suspected": "present when fn syncs or does host "
|
| 1869 |
+
"work inside; ops under ~50 us are "
|
| 1870 |
+
"below the detection floor",
|
| 1871 |
+
"verdict": "one-sentence summary",
|
| 1872 |
+
},
|
| 1873 |
+
"compare": {
|
| 1874 |
+
"pass": "final verdict incl. the one-sided bias gate",
|
| 1875 |
+
"verdict / criterion / hints": "summary, acceptance rule, and "
|
| 1876 |
+
"failure-pattern suggestions",
|
| 1877 |
+
"worst_trial": "max_ulp, mean_ulp, first_divergent_index(+_nd), "
|
| 1878 |
+
"NaN/Inf census, order_band_ulp, band_score, "
|
| 1879 |
+
"frac_above_band, bias_one_sided_frac, "
|
| 1880 |
+
"worst_elements (index, index_nd, ref, cand, ulp)",
|
| 1881 |
+
"max_abs / max_rel / ulp_hist_log2": "aggregate evidence",
|
| 1882 |
+
"floor": "the banded route cannot reject errors under "
|
| 1883 |
+
"band_margin*(band+2) ULP at order-stable elements",
|
| 1884 |
+
},
|
| 1885 |
+
"fuzz_shapes": {
|
| 1886 |
+
"cases": "per-case shape, variant, dtype, status, repro recipe",
|
| 1887 |
+
"status values": "ok | mismatch | exception | context_killed | "
|
| 1888 |
+
"context_lost | crashed_isolated | skipped_vram "
|
| 1889 |
+
"| skipped_empty",
|
| 1890 |
+
"failed / total / context_lost / verdict / hints": "aggregates",
|
| 1891 |
+
},
|
| 1892 |
+
"sweep": {
|
| 1893 |
+
"rows": "grid point plus median_ms, rejected_throttled, check?, "
|
| 1894 |
+
"achieved rates, status in pruned_race | not_run",
|
| 1895 |
+
"verdict": "on the fastest row",
|
| 1896 |
+
},
|
| 1897 |
+
"dossier_drift": {
|
| 1898 |
+
"entries": "path, current, pinned, rel per intersecting leaf",
|
| 1899 |
+
"worst_rel / over_tol / compared / pass / verdict": "aggregates",
|
| 1900 |
+
},
|
| 1901 |
+
"judge": {
|
| 1902 |
+
"pass / verdict / hints": "composite over compare, fuzz, bench",
|
| 1903 |
+
"compare / fuzz / bench": "the full sub-reports",
|
| 1904 |
+
},
|
| 1905 |
+
"read_stamps": {
|
| 1906 |
+
"phases": "ticks and us per phase; 2-D input adds min/median/max "
|
| 1907 |
+
"across blocks",
|
| 1908 |
+
"note": "stamps record issue; DRAIN variants include the store "
|
| 1909 |
+
"drain to the coherence point",
|
| 1910 |
+
},
|
| 1911 |
+
}
|
| 1912 |
+
|
| 1913 |
+
|
| 1914 |
+
def schema(name=None):
|
| 1915 |
+
"""Field layout of every report, so an agent can look up what a number means."""
|
| 1916 |
+
if name is None:
|
| 1917 |
+
return _SCHEMAS
|
| 1918 |
+
if name not in _SCHEMAS:
|
| 1919 |
+
raise KeyError(f"no schema for {name!r}; have "
|
| 1920 |
+
f"{sorted(_SCHEMAS)}")
|
| 1921 |
+
return _SCHEMAS[name]
|
build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8120f29036953f429c2640dbe6dcb057d44ff3ac89944c849d07f5abe6616c8d
|
| 3 |
+
size 1426104
|
build/torch213-cxx11-cu130-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:88438dc3c8221430094a0874eb102dc4eadcdfda40ba1c176ba9ff9ab3c21fef
|
| 3 |
-
size 1179960
|
|
|
|
|
|
|
|
|
|
|
|
build/torch213-cxx11-cu130-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _metakernel_cuda_0b9de42
|
| 3 |
+
ops = torch.ops._metakernel_cuda_0b9de42
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_metakernel_cuda_0b9de42::{op_name}"
|
build/torch213-cxx11-cu130-x86_64-linux/_pinned.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
Generated by dev/pin_dossier.py from full probe_device() runs on idle,
|
| 4 |
-
single-tenant cards; consumed by metakernel.dossier_drift.
|
| 5 |
-
"""
|
| 6 |
|
| 7 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 8 |
'confidence': 'high',
|
|
@@ -295,7 +291,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 295 |
'd2h_pinned': 10.8,
|
| 296 |
'h2d_pageable': 10.3,
|
| 297 |
'h2d_pinned': 11.8}}},
|
| 298 |
-
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.
|
| 299 |
'confidence': 'high',
|
| 300 |
'method': 'atomicAdd '
|
| 301 |
'hammer; curve '
|
|
@@ -308,21 +304,21 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 308 |
'contention) to '
|
| 309 |
'one per thread',
|
| 310 |
'units': 'Gop/s',
|
| 311 |
-
'value': {'curve_slots': {'1': 0.
|
| 312 |
-
'145408':
|
| 313 |
-
'4096':
|
| 314 |
-
'512':
|
| 315 |
-
'64': 13.
|
| 316 |
-
'8': 5.
|
| 317 |
-
'shared_same_addr': 12.
|
| 318 |
'clock': {'confidence': 'high',
|
| 319 |
-
'final_ghz': 2.
|
| 320 |
-
'max_ghz': 2.
|
| 321 |
'method': '24 back-to-back FMA '
|
| 322 |
'spin windows of 30M '
|
| 323 |
'ticks; wall/ticks '
|
| 324 |
'per window',
|
| 325 |
-
'min_ghz': 2.
|
| 326 |
'sag_pct': 0.3,
|
| 327 |
'units': 'GHz'},
|
| 328 |
'device': {'cuda_runtime': '12.6',
|
|
@@ -336,7 +332,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 336 |
'sms': 142,
|
| 337 |
'torch': '2.10.0+cu126',
|
| 338 |
'vram_bytes': 48305274880},
|
| 339 |
-
'dram_bw': {'clock_ghz': 2.
|
| 340 |
'confidence': 'high',
|
| 341 |
'method': 'streaming triad '
|
| 342 |
'c=a+s*b per access '
|
|
@@ -356,26 +352,41 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 356 |
'line-efficiency '
|
| 357 |
'claim',
|
| 358 |
'units': 'GB/s',
|
| 359 |
-
'value': {'random_gather_16B': 218.
|
| 360 |
-
'read_only':
|
| 361 |
-
'triad_16B':
|
| 362 |
-
'triad_4B':
|
| 363 |
-
'triad_8B':
|
| 364 |
'write_only': 766.5}},
|
| 365 |
-
'fma_f32': {'clock_ghz': 2.
|
| 366 |
'confidence': 'high',
|
| 367 |
'method': 'register-resident '
|
| 368 |
'FMA chains',
|
| 369 |
'units': 'TFLOP/s',
|
| 370 |
-
'value':
|
| 371 |
-
'fma_f64': {'clock_ghz': 2.
|
| 372 |
'confidence': 'high',
|
| 373 |
'method': 'register-resident '
|
| 374 |
'double FMA chains '
|
| 375 |
'(the DFMA pipe)',
|
| 376 |
'units': 'TFLOP/s',
|
| 377 |
-
'value': 1.
|
| 378 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
'confidence': 'high',
|
| 380 |
'method': 'cooperative '
|
| 381 |
'kernel, 142 '
|
|
@@ -383,8 +394,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 383 |
'threads',
|
| 384 |
'units': 'us per '
|
| 385 |
'grid.sync',
|
| 386 |
-
'value': 0.
|
| 387 |
-
'l2_bw': {'clock_ghz': 2.
|
| 388 |
'confidence': 'medium',
|
| 389 |
'method': 'in-kernel repeated '
|
| 390 |
'triad passes over 72 '
|
|
@@ -394,14 +405,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 394 |
'from HBM; amortized '
|
| 395 |
'over 64 passes',
|
| 396 |
'units': 'GB/s',
|
| 397 |
-
'value':
|
| 398 |
-
'latency': {'clock_ghz': 2.
|
| 399 |
'confidence': 'high',
|
| 400 |
'method': 'Sattolo-cycle '
|
| 401 |
-
'pointer chase
|
| 402 |
-
'
|
| 403 |
-
'
|
| 404 |
-
'ring
|
|
|
|
| 405 |
'self-evicting '
|
| 406 |
'chain (2x L2 in '
|
| 407 |
'touched lines) for '
|
|
@@ -413,40 +425,58 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 413 |
'footprints',
|
| 414 |
'units': 'cycles (single-lane '
|
| 415 |
'dependent loads)',
|
| 416 |
-
'value': {'dram_cycles':
|
| 417 |
'l1_cycles': 45.8,
|
| 418 |
-
'l2_cycles':
|
| 419 |
'ns_per_cycle': 0.372,
|
| 420 |
'smem_cycles': 30.0}},
|
| 421 |
-
'launch_overhead': {'clock_ghz': 2.
|
| 422 |
-
'confidence': '
|
| 423 |
'method': 'empty-kernel '
|
| 424 |
-
'trains
|
| 425 |
-
'
|
| 426 |
-
'launch
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
'units': 'us',
|
| 432 |
-
'value': {'
|
| 433 |
-
'
|
| 434 |
-
|
|
|
|
|
|
|
| 435 |
'confidence': 'medium',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
'method': 'register-fragment '
|
| 437 |
'mma_sync loops (wmma; '
|
| 438 |
'fp8 and 2:4-sparse '
|
| 439 |
'fp16 via inline PTX '
|
| 440 |
-
'm16n8k32;
|
| 441 |
-
'
|
| 442 |
-
'
|
| 443 |
-
'
|
| 444 |
-
'
|
| 445 |
-
'
|
| 446 |
-
'
|
| 447 |
-
'
|
| 448 |
-
'
|
| 449 |
-
'
|
| 450 |
'note': 'dense-tile rate without '
|
| 451 |
'memory traffic; real '
|
| 452 |
'kernels see this only '
|
|
@@ -454,25 +484,54 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 454 |
'behind it',
|
| 455 |
'units': 'TFLOP/s (TOP/s for '
|
| 456 |
'int8)',
|
| 457 |
-
'value': {'bf16':
|
| 458 |
-
'fp16':
|
| 459 |
-
'fp16_sparse_2_4':
|
| 460 |
-
|
| 461 |
-
'
|
| 462 |
-
'
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
| 464 |
'vs_nominal': 1.0},
|
| 465 |
-
'fp16': {'
|
|
|
|
| 466 |
'vs_nominal': 1.0},
|
| 467 |
-
'fp16_sparse_2_4': {'
|
|
|
|
| 468 |
'vs_nominal': 1.0},
|
| 469 |
-
'fp8_e4m3': {'
|
| 470 |
-
'
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
'vs_nominal': 1.0},
|
| 473 |
-
'tf32': {'
|
|
|
|
| 474 |
'vs_nominal': 1.0}}},
|
| 475 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
'confidence': 'high',
|
| 477 |
'method': 'occupancy '
|
| 478 |
'API '
|
|
@@ -501,7 +560,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 501 |
'16KB': 3,
|
| 502 |
'32KB': 3,
|
| 503 |
'48KB': 2}}},
|
| 504 |
-
'occupancy_knees_fma_tflops': {'clock_ghz': 2.
|
| 505 |
'confidence': 'high',
|
| 506 |
'method': 'a '
|
| 507 |
'single '
|
|
@@ -521,17 +580,6 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 521 |
'fixed '
|
| 522 |
'total '
|
| 523 |
'work; '
|
| 524 |
-
'throughput '
|
| 525 |
-
'is '
|
| 526 |
-
'proportional '
|
| 527 |
-
'to '
|
| 528 |
-
'resident '
|
| 529 |
-
'threads '
|
| 530 |
-
'until '
|
| 531 |
-
'the '
|
| 532 |
-
'pipe '
|
| 533 |
-
'saturates, '
|
| 534 |
-
'so '
|
| 535 |
'the '
|
| 536 |
'cliff '
|
| 537 |
'marks '
|
|
@@ -549,15 +597,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 549 |
'above',
|
| 550 |
'units': 'TFLOP/s',
|
| 551 |
'value': {'t1024': {'0KB': 75.5,
|
| 552 |
-
'32KB': 73.
|
| 553 |
-
'48KB':
|
| 554 |
-
't128': {'0KB': 79.
|
| 555 |
'32KB': 60.31,
|
| 556 |
-
'48KB':
|
| 557 |
-
't256': {'0KB':
|
| 558 |
-
'32KB':
|
| 559 |
-
'48KB':
|
| 560 |
-
'smem_bw': {'clock_ghz': 2.
|
| 561 |
'confidence': 'medium',
|
| 562 |
'method': 'block-resident '
|
| 563 |
'read+write '
|
|
@@ -566,12 +614,56 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 566 |
'aggregated across '
|
| 567 |
'SMs',
|
| 568 |
'note': 'bank-conflict-light '
|
| 569 |
-
'access pattern;
|
| 570 |
-
'
|
| 571 |
-
'
|
|
|
|
| 572 |
'units': 'GB/s',
|
| 573 |
-
'value':
|
| 574 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
'confidence': 'high',
|
| 576 |
'method': '256 MB torch '
|
| 577 |
'copies, '
|
|
@@ -583,8 +675,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 583 |
'direction of '
|
| 584 |
'traffic',
|
| 585 |
'units': 'GB/s',
|
| 586 |
-
'value': {'d2d':
|
| 587 |
-
'd2h_pageable':
|
| 588 |
-
'd2h_pinned': 26.
|
| 589 |
-
'h2d_pageable':
|
| 590 |
'h2d_pinned': 24.0}}}}
|
|
|
|
| 1 |
+
'''Release-pinned device dossiers, one per certified architecture; generated by dev/pin_dossier.py from idle single-tenant runs, consumed by dossier_drift.'''
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 4 |
'confidence': 'high',
|
|
|
|
| 291 |
'd2h_pinned': 10.8,
|
| 292 |
'h2d_pageable': 10.3,
|
| 293 |
'h2d_pinned': 11.8}}},
|
| 294 |
+
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.705,
|
| 295 |
'confidence': 'high',
|
| 296 |
'method': 'atomicAdd '
|
| 297 |
'hammer; curve '
|
|
|
|
| 304 |
'contention) to '
|
| 305 |
'one per thread',
|
| 306 |
'units': 'Gop/s',
|
| 307 |
+
'value': {'curve_slots': {'1': 0.71,
|
| 308 |
+
'145408': 413.99,
|
| 309 |
+
'4096': 184.9,
|
| 310 |
+
'512': 49.84,
|
| 311 |
+
'64': 13.25,
|
| 312 |
+
'8': 5.74},
|
| 313 |
+
'shared_same_addr': 12.23}},
|
| 314 |
'clock': {'confidence': 'high',
|
| 315 |
+
'final_ghz': 2.698,
|
| 316 |
+
'max_ghz': 2.705,
|
| 317 |
'method': '24 back-to-back FMA '
|
| 318 |
'spin windows of 30M '
|
| 319 |
'ticks; wall/ticks '
|
| 320 |
'per window',
|
| 321 |
+
'min_ghz': 2.5,
|
| 322 |
'sag_pct': 0.3,
|
| 323 |
'units': 'GHz'},
|
| 324 |
'device': {'cuda_runtime': '12.6',
|
|
|
|
| 332 |
'sms': 142,
|
| 333 |
'torch': '2.10.0+cu126',
|
| 334 |
'vram_bytes': 48305274880},
|
| 335 |
+
'dram_bw': {'clock_ghz': 2.705,
|
| 336 |
'confidence': 'high',
|
| 337 |
'method': 'streaming triad '
|
| 338 |
'c=a+s*b per access '
|
|
|
|
| 352 |
'line-efficiency '
|
| 353 |
'claim',
|
| 354 |
'units': 'GB/s',
|
| 355 |
+
'value': {'random_gather_16B': 218.1,
|
| 356 |
+
'read_only': 766.5,
|
| 357 |
+
'triad_16B': 682.1,
|
| 358 |
+
'triad_4B': 654.8,
|
| 359 |
+
'triad_8B': 670.9,
|
| 360 |
'write_only': 766.5}},
|
| 361 |
+
'fma_f32': {'clock_ghz': 2.705,
|
| 362 |
'confidence': 'high',
|
| 363 |
'method': 'register-resident '
|
| 364 |
'FMA chains',
|
| 365 |
'units': 'TFLOP/s',
|
| 366 |
+
'value': 86.58},
|
| 367 |
+
'fma_f64': {'clock_ghz': 2.705,
|
| 368 |
'confidence': 'high',
|
| 369 |
'method': 'register-resident '
|
| 370 |
'double FMA chains '
|
| 371 |
'(the DFMA pipe)',
|
| 372 |
'units': 'TFLOP/s',
|
| 373 |
+
'value': 1.257},
|
| 374 |
+
'fma_ilp': {'clock_ghz': 2.705,
|
| 375 |
+
'confidence': 'high',
|
| 376 |
+
'method': '1/2/4 independent '
|
| 377 |
+
'dependent FMA '
|
| 378 |
+
'chains per thread '
|
| 379 |
+
'at one block of 64 '
|
| 380 |
+
'threads per SM; a '
|
| 381 |
+
'ratio near 4 means '
|
| 382 |
+
'latency-bound at '
|
| 383 |
+
'that occupancy',
|
| 384 |
+
'units': 'TFLOP/s',
|
| 385 |
+
'value': {'ilp1': 11.75,
|
| 386 |
+
'ilp2': 23.37,
|
| 387 |
+
'ilp4': 43.03,
|
| 388 |
+
'ratio_4_1': 3.66}},
|
| 389 |
+
'grid_barrier': {'clock_ghz': 2.705,
|
| 390 |
'confidence': 'high',
|
| 391 |
'method': 'cooperative '
|
| 392 |
'kernel, 142 '
|
|
|
|
| 394 |
'threads',
|
| 395 |
'units': 'us per '
|
| 396 |
'grid.sync',
|
| 397 |
+
'value': 0.55},
|
| 398 |
+
'l2_bw': {'clock_ghz': 2.705,
|
| 399 |
'confidence': 'medium',
|
| 400 |
'method': 'in-kernel repeated '
|
| 401 |
'triad passes over 72 '
|
|
|
|
| 405 |
'from HBM; amortized '
|
| 406 |
'over 64 passes',
|
| 407 |
'units': 'GB/s',
|
| 408 |
+
'value': 9590.6},
|
| 409 |
+
'latency': {'clock_ghz': 2.691,
|
| 410 |
'confidence': 'high',
|
| 411 |
'method': 'Sattolo-cycle '
|
| 412 |
+
'pointer chase, min '
|
| 413 |
+
'of 3 runs; 16 KB '
|
| 414 |
+
'ring for L1, 2 MB '
|
| 415 |
+
'TLB-resident ring '
|
| 416 |
+
'for L2, '
|
| 417 |
'self-evicting '
|
| 418 |
'chain (2x L2 in '
|
| 419 |
'touched lines) for '
|
|
|
|
| 425 |
'footprints',
|
| 426 |
'units': 'cycles (single-lane '
|
| 427 |
'dependent loads)',
|
| 428 |
+
'value': {'dram_cycles': 656.9,
|
| 429 |
'l1_cycles': 45.8,
|
| 430 |
+
'l2_cycles': 292.0,
|
| 431 |
'ns_per_cycle': 0.372,
|
| 432 |
'smem_cycles': 30.0}},
|
| 433 |
+
'launch_overhead': {'clock_ghz': 2.705,
|
| 434 |
+
'confidence': 'medium',
|
| 435 |
'method': 'empty-kernel '
|
| 436 |
+
'trains; '
|
| 437 |
+
'torch path '
|
| 438 |
+
'per-launch '
|
| 439 |
+
'through op '
|
| 440 |
+
'dispatch, '
|
| 441 |
+
'raw path '
|
| 442 |
+
'looped '
|
| 443 |
+
'inside one '
|
| 444 |
+
'binding '
|
| 445 |
+
'call',
|
| 446 |
+
'note': 'host-state-dependent; '
|
| 447 |
+
'the '
|
| 448 |
+
'torch-minus-raw '
|
| 449 |
+
'gap is the '
|
| 450 |
+
'dispatch '
|
| 451 |
+
'cost a '
|
| 452 |
+
'torch-launched '
|
| 453 |
+
'candidate '
|
| 454 |
+
'pays',
|
| 455 |
'units': 'us',
|
| 456 |
+
'value': {'raw_queued_us': 6.75,
|
| 457 |
+
'raw_round_trip_us': 13.87,
|
| 458 |
+
'torch_queued_us': 7.09,
|
| 459 |
+
'torch_round_trip_us': 18.53}},
|
| 460 |
+
'mma': {'clock_ghz': 2.705,
|
| 461 |
'confidence': 'medium',
|
| 462 |
+
'fp8_accum': {'fp8_e4m3': {'effective_accum_mantissa_bits': 8,
|
| 463 |
+
'stall_element_value': 16384.0},
|
| 464 |
+
'fp8_e5m2': {'effective_accum_mantissa_bits': 8,
|
| 465 |
+
'stall_element_value': 16384.0}},
|
| 466 |
'method': 'register-fragment '
|
| 467 |
'mma_sync loops (wmma; '
|
| 468 |
'fp8 and 2:4-sparse '
|
| 469 |
'fp16 via inline PTX '
|
| 470 |
+
'm16n8k32); flops '
|
| 471 |
+
'derive from a '
|
| 472 |
+
'sub-saturation '
|
| 473 |
+
'accumulator read-back '
|
| 474 |
+
'with every fragment '
|
| 475 |
+
'element observed, '
|
| 476 |
+
'never the declared '
|
| 477 |
+
'loop shape; sparse '
|
| 478 |
+
'reports executed and '
|
| 479 |
+
'dense-equivalent flops',
|
| 480 |
'note': 'dense-tile rate without '
|
| 481 |
'memory traffic; real '
|
| 482 |
'kernels see this only '
|
|
|
|
| 484 |
'behind it',
|
| 485 |
'units': 'TFLOP/s (TOP/s for '
|
| 486 |
'int8)',
|
| 487 |
+
'value': {'bf16': 369.6,
|
| 488 |
+
'fp16': 365.3,
|
| 489 |
+
'fp16_sparse_2_4': {'dense_equivalent': 711.1,
|
| 490 |
+
'executed': 355.6},
|
| 491 |
+
'fp8_e4m3': 772.3,
|
| 492 |
+
'fp8_e5m2': 771.2,
|
| 493 |
+
'int8': 750.0,
|
| 494 |
+
'tf32': 178.3},
|
| 495 |
+
'verified_k_lanes': {'bf16': {'calibration_linear': True,
|
| 496 |
+
'executed_k_per_iter': 64.0,
|
| 497 |
'vs_nominal': 1.0},
|
| 498 |
+
'fp16': {'calibration_linear': True,
|
| 499 |
+
'executed_k_per_iter': 64.0,
|
| 500 |
'vs_nominal': 1.0},
|
| 501 |
+
'fp16_sparse_2_4': {'calibration_linear': True,
|
| 502 |
+
'executed_k_per_iter': 128.0,
|
| 503 |
'vs_nominal': 1.0},
|
| 504 |
+
'fp8_e4m3': {'calibration_linear': True,
|
| 505 |
+
'executed_k_per_iter': 256.0,
|
| 506 |
+
'vs_nominal': 1.0},
|
| 507 |
+
'fp8_e5m2': {'calibration_linear': True,
|
| 508 |
+
'executed_k_per_iter': 256.0,
|
| 509 |
+
'vs_nominal': 1.0},
|
| 510 |
+
'int8': {'calibration_linear': True,
|
| 511 |
+
'executed_k_per_iter': 64.0,
|
| 512 |
'vs_nominal': 1.0},
|
| 513 |
+
'tf32': {'calibration_linear': True,
|
| 514 |
+
'executed_k_per_iter': 32.0,
|
| 515 |
'vs_nominal': 1.0}}},
|
| 516 |
+
'mma_fed': {'clock_ghz': 2.705,
|
| 517 |
+
'confidence': 'high',
|
| 518 |
+
'method': 'm16n16k16 fp16 '
|
| 519 |
+
'tiles fed from an '
|
| 520 |
+
'L2-resident buffer '
|
| 521 |
+
'through cp.async '
|
| 522 |
+
'double-buffered '
|
| 523 |
+
'shared memory; '
|
| 524 |
+
'accumulator-verified',
|
| 525 |
+
'note': 'the achieved '
|
| 526 |
+
'counterpart to the '
|
| 527 |
+
'register peak: '
|
| 528 |
+
'staging exposed, one '
|
| 529 |
+
'dependent '
|
| 530 |
+
'accumulator chain '
|
| 531 |
+
'per warp',
|
| 532 |
+
'units': 'TFLOP/s',
|
| 533 |
+
'value': 85.2},
|
| 534 |
+
'occupancy_blocks_per_sm': {'clock_ghz': 2.705,
|
| 535 |
'confidence': 'high',
|
| 536 |
'method': 'occupancy '
|
| 537 |
'API '
|
|
|
|
| 560 |
'16KB': 3,
|
| 561 |
'32KB': 3,
|
| 562 |
'48KB': 2}}},
|
| 563 |
+
'occupancy_knees_fma_tflops': {'clock_ghz': 2.705,
|
| 564 |
'confidence': 'high',
|
| 565 |
'method': 'a '
|
| 566 |
'single '
|
|
|
|
| 580 |
'fixed '
|
| 581 |
'total '
|
| 582 |
'work; '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
'the '
|
| 584 |
'cliff '
|
| 585 |
'marks '
|
|
|
|
| 597 |
'above',
|
| 598 |
'units': 'TFLOP/s',
|
| 599 |
'value': {'t1024': {'0KB': 75.5,
|
| 600 |
+
'32KB': 73.71,
|
| 601 |
+
'48KB': 72.69},
|
| 602 |
+
't128': {'0KB': 79.72,
|
| 603 |
'32KB': 60.31,
|
| 604 |
+
'48KB': 45.81},
|
| 605 |
+
't256': {'0KB': 78.79,
|
| 606 |
+
'32KB': 78.85,
|
| 607 |
+
'48KB': 72.97}}},
|
| 608 |
+
'smem_bw': {'clock_ghz': 2.705,
|
| 609 |
'confidence': 'medium',
|
| 610 |
'method': 'block-resident '
|
| 611 |
'read+write '
|
|
|
|
| 614 |
'aggregated across '
|
| 615 |
'SMs',
|
| 616 |
'note': 'bank-conflict-light '
|
| 617 |
+
'access pattern; the '
|
| 618 |
+
'stride sweep below '
|
| 619 |
+
'prices the '
|
| 620 |
+
'conflicted case',
|
| 621 |
'units': 'GB/s',
|
| 622 |
+
'value': 22244.0},
|
| 623 |
+
'smem_conflict_penalty': {'clock_ghz': 2.705,
|
| 624 |
+
'confidence': 'high',
|
| 625 |
+
'method': 'dependent '
|
| 626 |
+
'read-FMA-write '
|
| 627 |
+
'per '
|
| 628 |
+
'thread '
|
| 629 |
+
'at '
|
| 630 |
+
'warp-lane '
|
| 631 |
+
'bank '
|
| 632 |
+
'distance '
|
| 633 |
+
'`stride`; '
|
| 634 |
+
'stride '
|
| 635 |
+
'32 '
|
| 636 |
+
'puts '
|
| 637 |
+
'every '
|
| 638 |
+
'lane '
|
| 639 |
+
'in '
|
| 640 |
+
'one '
|
| 641 |
+
'bank',
|
| 642 |
+
'note': 'the '
|
| 643 |
+
'direct '
|
| 644 |
+
'conflict '
|
| 645 |
+
'measurement '
|
| 646 |
+
'that '
|
| 647 |
+
'disambiguates '
|
| 648 |
+
'a '
|
| 649 |
+
'bank-conflict '
|
| 650 |
+
'signature '
|
| 651 |
+
'from '
|
| 652 |
+
'an ILP '
|
| 653 |
+
'stall',
|
| 654 |
+
'units': 'x '
|
| 655 |
+
'(time '
|
| 656 |
+
'ratio '
|
| 657 |
+
'vs '
|
| 658 |
+
'stride '
|
| 659 |
+
'1)',
|
| 660 |
+
'value': {'stride_1': 1.0,
|
| 661 |
+
'stride_16': 7.13,
|
| 662 |
+
'stride_2': 1.07,
|
| 663 |
+
'stride_32': 14.08,
|
| 664 |
+
'stride_4': 2.04,
|
| 665 |
+
'stride_8': 3.91}},
|
| 666 |
+
'transfer_bw': {'clock_ghz': 2.705,
|
| 667 |
'confidence': 'high',
|
| 668 |
'method': '256 MB torch '
|
| 669 |
'copies, '
|
|
|
|
| 675 |
'direction of '
|
| 676 |
'traffic',
|
| 677 |
'units': 'GB/s',
|
| 678 |
+
'value': {'d2d': 324.0,
|
| 679 |
+
'd2h_pageable': 7.8,
|
| 680 |
+
'd2h_pinned': 26.3,
|
| 681 |
+
'h2d_pageable': 7.6,
|
| 682 |
'h2d_pinned': 24.0}}}}
|
build/torch213-cxx11-cu130-x86_64-linux/metadata.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 7 |
"backend": {
|
| 8 |
"type": "cuda",
|
| 9 |
"archs": [
|
|
@@ -18,21 +19,20 @@
|
|
| 18 |
"digest": {
|
| 19 |
"algorithm": "sha256",
|
| 20 |
"files": {
|
| 21 |
-
"__init__.py": "
|
| 22 |
-
"
|
| 23 |
-
"_ops.py": "
|
| 24 |
-
"_pinned.py": "
|
| 25 |
-
"metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"provenance": {
|
| 29 |
"kernel-builder": {
|
| 30 |
"version": "0.17.0-dev0",
|
| 31 |
-
"sha": "
|
| 32 |
"dirty": false
|
| 33 |
},
|
| 34 |
"kernel": {
|
| 35 |
-
"sha": "
|
| 36 |
"dirty": false
|
| 37 |
}
|
| 38 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
+
"id": "_metakernel_cuda_0b9de42",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
| 7 |
+
"kernel-depends": [],
|
| 8 |
"backend": {
|
| 9 |
"type": "cuda",
|
| 10 |
"archs": [
|
|
|
|
| 19 |
"digest": {
|
| 20 |
"algorithm": "sha256",
|
| 21 |
"files": {
|
| 22 |
+
"__init__.py": "lDQ+fJ0JnI3sQGR3UMnDovBbG4ciYBsaFjmgYdKDTKI=",
|
| 23 |
+
"_metakernel_cuda_0b9de42.abi3.so": "gSDykDaVP0KcJkDb5tywV9RP86yJlEyEnQf1q+ZhbI0=",
|
| 24 |
+
"_ops.py": "x88Bkm1nxRli0nIu2u4ztaJopS1IAZWdN9LZPfspJdo=",
|
| 25 |
+
"_pinned.py": "UyzMniAjfVMIrIbbrSq92KKo5dX5l/GwV3pMVA+eNtM="
|
|
|
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"provenance": {
|
| 29 |
"kernel-builder": {
|
| 30 |
"version": "0.17.0-dev0",
|
| 31 |
+
"sha": "2c40e10d3da1392245ee81bd05b55fb3103b2b34",
|
| 32 |
"dirty": false
|
| 33 |
},
|
| 34 |
"kernel": {
|
| 35 |
+
"sha": "0b9de42fee8a1e52593f2f1f58460c0acc93d7e8",
|
| 36 |
"dirty": false
|
| 37 |
}
|
| 38 |
}
|
build/torch213-cxx11-cu130-x86_64-linux/metakernel/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 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
CHANGED
|
@@ -1,20 +1,4 @@
|
|
| 1 |
-
"""Measuring instrument for the kernel-design loop: timing, adversarial
|
| 2 |
-
correctness comparison, and measured device facts, as loadable ops.
|
| 3 |
-
|
| 4 |
-
from kernels import get_kernel
|
| 5 |
-
mk = get_kernel("phanerozoic/metakernel", version=1,
|
| 6 |
-
trust_remote_code=True)
|
| 7 |
-
|
| 8 |
-
dossier = mk.probe_device() # measured facts
|
| 9 |
-
report = mk.bench(lambda: op(x), iters=200) # throttle-rejected
|
| 10 |
-
verdict = mk.compare(ref, cand, gen) # ULP evidence
|
| 11 |
-
table = mk.sweep(factory, {"BLOCK": [64, 128]}) # config search
|
| 12 |
-
|
| 13 |
-
Every op returns plain dicts and tensors. Every dossier entry is
|
| 14 |
-
measured by a dedicated probe kernel, carries its SM clock, and labels
|
| 15 |
-
throttled samples; no hardware counters, so entries carry a confidence
|
| 16 |
-
field.
|
| 17 |
-
"""
|
| 18 |
import math
|
| 19 |
import os
|
| 20 |
import time
|
|
@@ -24,9 +8,7 @@ import torch
|
|
| 24 |
try:
|
| 25 |
from ._ops import ops
|
| 26 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 27 |
-
#
|
| 28 |
-
# checkout there is no generated _ops module, so rebuild the op table
|
| 29 |
-
# from the (cached) JIT extension that load_local advertised.
|
| 30 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 31 |
if not _root:
|
| 32 |
raise
|
|
@@ -48,24 +30,25 @@ try:
|
|
| 48 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 49 |
_PINNED = {}
|
| 50 |
|
| 51 |
-
__all__ = ["probe_device", "bench", "compare", "
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"pinned_dossiers", "
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
#
|
| 62 |
_MMA_KINDS = {
|
| 63 |
-
"fp16": (0, 16 * 16, 16,
|
| 64 |
-
"bf16": (1, 16 * 16, 16,
|
| 65 |
-
"tf32": (2, 16 * 16, 8,
|
| 66 |
-
"int8": (3, 16 * 16, 16,
|
| 67 |
-
"fp8_e4m3": (4, 16 * 8, 32,
|
| 68 |
-
"fp16_sparse_2_4": (5, 16 * 8, 32,
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
|
|
@@ -74,7 +57,7 @@ def _sync():
|
|
| 74 |
|
| 75 |
|
| 76 |
def _events(fn, reps):
|
| 77 |
-
"""Time reps calls of fn with one
|
| 78 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 79 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 80 |
for s, e in zip(starts, stops):
|
|
@@ -86,8 +69,7 @@ def _events(fn, reps):
|
|
| 86 |
|
| 87 |
|
| 88 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 89 |
-
"""Effective SM clock in GHz
|
| 90 |
-
Wall time over elapsed ticks is the frequency the ticks ran at."""
|
| 91 |
props = torch.cuda.get_device_properties(device)
|
| 92 |
if blocks is None:
|
| 93 |
blocks = props.multi_processor_count
|
|
@@ -120,8 +102,7 @@ def _entry(value, units, method, clock_ghz, confidence, note=""):
|
|
| 120 |
|
| 121 |
|
| 122 |
def _sattolo_ring(n, device, generator=None):
|
| 123 |
-
"""
|
| 124 |
-
a pointer chase visits every slot before repeating."""
|
| 125 |
perm = torch.randperm(n, generator=generator)
|
| 126 |
ring = torch.empty(n, dtype=torch.int32)
|
| 127 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
@@ -129,8 +110,7 @@ def _sattolo_ring(n, device, generator=None):
|
|
| 129 |
|
| 130 |
|
| 131 |
def _smi_settings(index):
|
| 132 |
-
"""ECC mode, driver,
|
| 133 |
-
when the tool is unavailable."""
|
| 134 |
import subprocess
|
| 135 |
try:
|
| 136 |
out = subprocess.run(
|
|
@@ -150,10 +130,19 @@ def _smi_settings(index):
|
|
| 150 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 151 |
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def mma_tile_check(device="cuda", trials=8):
|
| 154 |
-
"""Random small-integer m16n16k16 tiles through the wmma
|
| 155 |
-
path, compared exactly to the reference GEMM; pins M, N, K, and
|
| 156 |
-
operand mapping (integer operands make fp32 accumulation exact)."""
|
| 157 |
worst = 0.0
|
| 158 |
for t in range(trials):
|
| 159 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
@@ -171,10 +160,83 @@ def mma_tile_check(device="cuda", trials=8):
|
|
| 171 |
"trials": trials}
|
| 172 |
|
| 173 |
|
| 174 |
-
def
|
| 175 |
-
"""
|
| 176 |
-
|
| 177 |
-
a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
props = torch.cuda.get_device_properties(device)
|
| 179 |
sm = props.multi_processor_count
|
| 180 |
l2 = props.L2_cache_size
|
|
@@ -187,8 +249,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 187 |
"vram_bytes": props.total_memory,
|
| 188 |
"torch": torch.__version__,
|
| 189 |
"cuda_runtime": torch.version.cuda,
|
| 190 |
-
# instrument settings
|
| 191 |
-
# dossier records them (None when nvidia-smi is unavailable)
|
| 192 |
**_smi_settings(torch.device(device).index or 0),
|
| 193 |
}
|
| 194 |
}
|
|
@@ -213,9 +274,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 213 |
}
|
| 214 |
clk = max(traj)
|
| 215 |
|
| 216 |
-
# ---- device-memory bandwidth
|
| 217 |
-
|
| 218 |
-
n4 = 16 << 20 # 16M float4 = 256 MB per buffer
|
| 219 |
if props.total_memory < 4 << 30:
|
| 220 |
n4 = 4 << 20
|
| 221 |
a = torch.rand(n4 * 4, device=device)
|
|
@@ -239,7 +299,6 @@ def probe_device(device="cuda", quick=False):
|
|
| 239 |
dtype=torch.int32).to(device)
|
| 240 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 241 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
| 242 |
-
# traffic accounting: idx reads + 16B random loads + streamed stores
|
| 243 |
hbm["random_gather_16B"] = round(
|
| 244 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 245 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
@@ -250,9 +309,9 @@ def probe_device(device="cuda", quick=False):
|
|
| 250 |
"so its nominal number is the achievable rate for "
|
| 251 |
"random access, not a line-efficiency claim")
|
| 252 |
|
| 253 |
-
# ---- L2 bandwidth
|
| 254 |
ws = max(l2 // 4, 1 << 20)
|
| 255 |
-
nl2 = ws // 16
|
| 256 |
al = a[:nl2 * 4].contiguous()
|
| 257 |
bl = b[:nl2 * 4].contiguous()
|
| 258 |
cl = c[:nl2 * 4].contiguous()
|
|
@@ -267,62 +326,61 @@ def probe_device(device="cuda", quick=False):
|
|
| 267 |
"medium", "first pass streams from HBM; amortized "
|
| 268 |
f"over {passes} passes")
|
| 269 |
|
| 270 |
-
# ---- latencies:
|
| 271 |
-
# L1, fits L2 everywhere, and stays TLB-resident after a warm walk;
|
| 272 |
-
# the DRAM chain touches 2x L2 in lines, so it evicts itself.
|
| 273 |
lat = {}
|
| 274 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
| 275 |
-
ring_s = _sattolo_ring(8192, device)
|
| 276 |
-
ops.mk_chase_shared(ring_s, 1 << 14, out64)
|
| 277 |
-
_sync()
|
| 278 |
-
hops = 1 << 16
|
| 279 |
-
ops.mk_chase_shared(ring_s, hops, out64)
|
| 280 |
-
_sync()
|
| 281 |
-
lat["smem_cycles"] = round(int(out64[0].item()) / hops, 1)
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
_sync()
|
| 299 |
-
lat["l2_cycles"] = round(int(out64[0].item()) / hops_l2, 1)
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 302 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 303 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
_sync()
|
| 308 |
-
lat["dram_cycles"] = round(int(out64[0].item()) / hops_h, 1)
|
| 309 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 310 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 311 |
-
"Sattolo-cycle pointer chase
|
| 312 |
-
"2 MB TLB-resident ring for L2,
|
| 313 |
-
"chain (2x L2 in touched lines)
|
| 314 |
-
idle_clk, "high",
|
| 315 |
"smem includes index arithmetic; HBM includes "
|
| 316 |
"residual TLB effects at large footprints")
|
| 317 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 318 |
|
| 319 |
-
# ---- fp32 FMA and tensor-core throughput
|
| 320 |
blocks = sm * 4
|
| 321 |
iters = 20_000 if quick else 60_000
|
| 322 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 323 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 324 |
reps))
|
| 325 |
-
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 326 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 327 |
"register-resident FMA chains", clk, "high")
|
| 328 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
@@ -334,43 +392,88 @@ def probe_device(device="cuda", quick=False):
|
|
| 334 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 335 |
"register-resident double FMA chains (the DFMA "
|
| 336 |
"pipe)", clk, "high")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
mma = {}
|
| 338 |
mma_klanes = {}
|
| 339 |
-
|
|
|
|
| 340 |
it = 30_000 if quick else 100_000
|
| 341 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 342 |
if r != 0:
|
| 343 |
mma[name] = "unsupported"
|
| 344 |
continue
|
| 345 |
_sync()
|
| 346 |
-
|
| 347 |
-
# the executed K-lane count (exact at these magnitudes)
|
| 348 |
-
cal_iters = 512
|
| 349 |
-
sink[1] = 0.0
|
| 350 |
-
ops.mk_mma(kind, 1, 1, cal_iters, sink)
|
| 351 |
-
_sync()
|
| 352 |
-
gain_per_iter = float(sink[1].item()) / cal_iters
|
| 353 |
mma_klanes[name] = {
|
| 354 |
-
"executed_k_per_iter": round(
|
| 355 |
-
"vs_nominal": round(
|
|
|
|
| 356 |
}
|
|
|
|
|
|
|
| 357 |
ms = _median(_events(
|
| 358 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 362 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 363 |
-
"2:4-sparse fp16 via inline PTX m16n8k32;
|
| 364 |
-
"
|
| 365 |
-
"flops derive from the mean accumulator read-back "
|
| 366 |
"with every fragment element observed, never the "
|
| 367 |
-
"declared loop shape"
|
|
|
|
| 368 |
clk, "medium",
|
| 369 |
"dense-tile rate without memory traffic; real "
|
| 370 |
"kernels see this only when staging hides behind it")
|
| 371 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
# ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
it_sm = 30_000 if quick else 100_000
|
| 375 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 376 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
@@ -380,8 +483,23 @@ def probe_device(device="cuda", quick=False):
|
|
| 380 |
"block-resident read+write streaming (8 B per "
|
| 381 |
"thread-iteration), aggregated across SMs", clk,
|
| 382 |
"medium",
|
| 383 |
-
"bank-conflict-light access pattern;
|
| 384 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
# ---- PCIe / host transfer bandwidth
|
| 387 |
nbytes = 256 << 20
|
|
@@ -412,9 +530,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 412 |
"d2d counts one direction of traffic")
|
| 413 |
del host_pin, host_pag, dev_t, dev_u
|
| 414 |
|
| 415 |
-
# ---- atomic contention curve
|
| 416 |
-
# addresses the whole grid hammers (slots = 1 is full contention;
|
| 417 |
-
# slots = threads is contention-free), plus the shared-memory point
|
| 418 |
n_thr = blocks * 256
|
| 419 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 420 |
at = {"curve_slots": {}}
|
|
@@ -424,7 +540,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 424 |
buf.zero_()
|
| 425 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 426 |
ms = _median(_events(
|
| 427 |
-
lambda
|
| 428 |
reps))
|
| 429 |
at["curve_slots"][str(slots)] = round(
|
| 430 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
@@ -437,7 +553,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 437 |
"of distinct global addresses from 1 (full "
|
| 438 |
"contention) to one per thread", clk, "high")
|
| 439 |
|
| 440 |
-
# ---- launch overhead
|
| 441 |
anchor = sink
|
| 442 |
n_launch = 500 if quick else 2000
|
| 443 |
_sync()
|
|
@@ -452,10 +568,19 @@ def probe_device(device="cuda", quick=False):
|
|
| 452 |
ops.mk_empty(anchor)
|
| 453 |
_sync()
|
| 454 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
|
|
|
|
|
|
| 455 |
d["launch_overhead"] = _entry(
|
| 456 |
-
{"
|
| 457 |
-
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
# ---- grid barrier
|
| 461 |
bar_iters = 500 if quick else 2000
|
|
@@ -471,9 +596,7 @@ def probe_device(device="cuda", quick=False):
|
|
| 471 |
"unavailable or grid too large", clk,
|
| 472 |
"high")
|
| 473 |
|
| 474 |
-
# ---- occupancy
|
| 475 |
-
# at every (threads, dyn_smem) point; when the forced blocks/SM stop
|
| 476 |
-
# covering memory latency, achieved bandwidth falls off the cliff
|
| 477 |
occ = {}
|
| 478 |
for threads in (128, 256, 512, 1024):
|
| 479 |
row = {}
|
|
@@ -494,8 +617,8 @@ def probe_device(device="cuda", quick=False):
|
|
| 494 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 495 |
knee_iters, sink)
|
| 496 |
ms = _median(_events(
|
| 497 |
-
lambda t=threads,
|
| 498 |
-
knee_blocks, t,
|
| 499 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 500 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 501 |
knees[f"t{threads}"] = row
|
|
@@ -504,27 +627,160 @@ def probe_device(device="cuda", quick=False):
|
|
| 504 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 505 |
"the cliff marks where occupancy stops covering latency",
|
| 506 |
clk, "high", "timing-derived counterpart to the API table above")
|
|
|
|
|
|
|
|
|
|
| 507 |
return d
|
| 508 |
|
| 509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 511 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 512 |
-
device="cuda"):
|
| 513 |
-
"""Median-of-N cudaEvent timing with throttle rejection.
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
around every sample, ~0.3 ms each."""
|
| 523 |
for _ in range(warmup):
|
| 524 |
fn()
|
| 525 |
_sync()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
graph_state = "off"
|
| 527 |
graph_reason = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
if graph:
|
| 529 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 530 |
try:
|
|
@@ -539,10 +795,7 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 539 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 540 |
graph_state = "fallback_eager"
|
| 541 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 542 |
-
# drain the invalidated stream
|
| 543 |
-
# On some torch versions a failed capture also wedges the
|
| 544 |
-
# device RNG with no in-process recovery; timing uses no RNG,
|
| 545 |
-
# but the caller's later randn may raise.
|
| 546 |
rng_ok = True
|
| 547 |
for _attempt in range(5):
|
| 548 |
try:
|
|
@@ -572,25 +825,51 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 572 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 573 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 574 |
best_clk = max(clocks)
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
flat = []
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
kept.append(ms)
|
| 585 |
else:
|
| 586 |
rejected += 1
|
| 587 |
-
|
|
|
|
| 588 |
kept = flat
|
| 589 |
med = _median(kept)
|
| 590 |
ks = sorted(kept)
|
| 591 |
n_k = len(ks)
|
| 592 |
-
# order-statistic 95% CI on the median
|
| 593 |
-
# binomial ranks); degenerate at tiny n, where it spans the sample
|
| 594 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 595 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 596 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
@@ -605,12 +884,25 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 605 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 606 |
"samples": len(flat),
|
| 607 |
"rejected_throttled": rejected,
|
|
|
|
| 608 |
"graph_captured": graph_state == "captured",
|
| 609 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 610 |
"worst": round(min(clocks), 3)},
|
| 611 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
if graph_reason:
|
| 613 |
rep["graph_fallback_reason"] = graph_reason
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
if bytes is not None:
|
| 615 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 616 |
if flops is not None:
|
|
@@ -620,6 +912,8 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 620 |
if dossier is not None and bytes and flops:
|
| 621 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 622 |
mma_v = dossier["mma"]["value"].get(dtype)
|
|
|
|
|
|
|
| 623 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 624 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 625 |
ai = flops / bytes
|
|
@@ -630,13 +924,12 @@ def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
|
| 630 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 631 |
"peak_dtype": dtype,
|
| 632 |
}
|
|
|
|
| 633 |
return rep
|
| 634 |
|
| 635 |
|
| 636 |
def _to_ordered_int(t):
|
| 637 |
-
"""
|
| 638 |
-
distance: positive floats keep their bits, negative floats mirror
|
| 639 |
-
below zero, so the mapping is monotone in the float value."""
|
| 640 |
if t.dtype == torch.float32:
|
| 641 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 642 |
half = 1 << 31
|
|
@@ -652,11 +945,12 @@ def _to_ordered_int(t):
|
|
| 652 |
|
| 653 |
|
| 654 |
def ulp_diff(a, b):
|
| 655 |
-
"""Elementwise ULP distance
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
|
|
|
| 660 |
if not a.is_floating_point():
|
| 661 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 662 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
@@ -673,9 +967,7 @@ class order_shuffles:
|
|
| 673 |
|
| 674 |
@staticmethod
|
| 675 |
def last_dim():
|
| 676 |
-
"""
|
| 677 |
-
accumulation order of a last-dim reduction changes, the value
|
| 678 |
-
does not."""
|
| 679 |
def shuffle(args, seed):
|
| 680 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 681 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
@@ -685,8 +977,7 @@ class order_shuffles:
|
|
| 685 |
|
| 686 |
@staticmethod
|
| 687 |
def matmul_k():
|
| 688 |
-
"""
|
| 689 |
-
product is unchanged, the accumulation order is not."""
|
| 690 |
def shuffle(args, seed):
|
| 691 |
a_m, b_m = args[0], args[1]
|
| 692 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
@@ -700,37 +991,70 @@ def _as_outputs(x):
|
|
| 700 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 701 |
|
| 702 |
|
| 703 |
-
def
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
worst = {"max_ulp": -1}
|
| 722 |
total_hist = {}
|
| 723 |
per_trial = []
|
| 724 |
max_abs_seen = 0.0
|
| 725 |
max_rel_seen = 0.0
|
|
|
|
| 726 |
for t in range(trials):
|
| 727 |
args = gen(seed + t)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
r_outs = _as_outputs(ref(*args))
|
| 729 |
c_outs = _as_outputs(cand(*args))
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
bands = None
|
| 735 |
band = 0
|
| 736 |
band_score = 0.0
|
|
@@ -754,8 +1078,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 754 |
max_ulp = 0
|
| 755 |
mean_ulp = 0.0
|
| 756 |
first_div = -1
|
|
|
|
| 757 |
n_el = 0
|
| 758 |
n_above_band = 0
|
|
|
|
|
|
|
| 759 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 760 |
worst_elements = []
|
| 761 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
@@ -773,8 +1100,11 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 773 |
mean_ulp += float(d.double().sum().item())
|
| 774 |
n_el += d.numel()
|
| 775 |
nz = (d > 0)
|
|
|
|
|
|
|
| 776 |
if first_div < 0 and nz.any():
|
| 777 |
first_div = int(nz.flatten().nonzero()[0].item())
|
|
|
|
| 778 |
if nz.any():
|
| 779 |
buckets = torch.clamp(
|
| 780 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
@@ -791,6 +1121,7 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 791 |
if val > 0:
|
| 792 |
worst_elements.append({
|
| 793 |
"output": oi, "index": int(ix),
|
|
|
|
| 794 |
"ref": float(rf[ix].item()),
|
| 795 |
"cand": float(cf[ix].item()),
|
| 796 |
"ulp": int(val)})
|
|
@@ -810,23 +1141,27 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 810 |
inf_r += int(torch.isinf(r).sum().item())
|
| 811 |
inf_c += int(torch.isinf(c).sum().item())
|
| 812 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
|
|
|
| 813 |
trial_rep = {
|
| 814 |
"max_ulp": max_ulp,
|
| 815 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 816 |
"first_divergent_index": first_div,
|
|
|
|
| 817 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 818 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 819 |
"order_band_ulp": band,
|
| 820 |
"band_score": round(band_score, 2),
|
| 821 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
|
|
|
|
|
|
| 822 |
"worst_elements": worst_elements[:worst_k],
|
| 823 |
}
|
| 824 |
per_trial.append(trial_rep)
|
| 825 |
if max_ulp > worst["max_ulp"]:
|
| 826 |
worst = trial_rep
|
| 827 |
-
band_max = max(t["order_band_ulp"] for t in per_trial)
|
| 828 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 829 |
-
|
|
|
|
| 830 |
score = max(t["band_score"] for t in per_trial)
|
| 831 |
ulp_ok = score <= band_margin
|
| 832 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
@@ -841,8 +1176,34 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 841 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 842 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 843 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 844 |
-
|
| 845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
"criterion": criterion,
|
| 847 |
"worst_trial": worst,
|
| 848 |
"max_abs": max_abs_seen,
|
|
@@ -850,6 +1211,9 @@ def compare(ref, cand, gen, trials=8, order_shuffle=None, order_trials=8,
|
|
| 850 |
"trials": per_trial,
|
| 851 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 852 |
}
|
|
|
|
|
|
|
|
|
|
| 853 |
|
| 854 |
|
| 855 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
@@ -865,6 +1229,30 @@ def _context_alive(device):
|
|
| 865 |
return False
|
| 866 |
|
| 867 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
def _fuzz_build(make, shape, dtype):
|
| 869 |
try:
|
| 870 |
out = make(tuple(shape), dtype)
|
|
@@ -881,7 +1269,7 @@ def _fuzz_variant(x, v):
|
|
| 881 |
if v == "strided_rows":
|
| 882 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 883 |
if v == "offset_slice":
|
| 884 |
-
#
|
| 885 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 886 |
if v == "overflow_expand":
|
| 887 |
if x.numel() == 0:
|
|
@@ -895,8 +1283,7 @@ def _fuzz_variant(x, v):
|
|
| 895 |
|
| 896 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 897 |
stop_on_context_loss=True):
|
| 898 |
-
"""All variant cases for one (shape, dtype) group; returns (rows,
|
| 899 |
-
context_lost)."""
|
| 900 |
rows = []
|
| 901 |
bases = _fuzz_build(make, shape, dtype)
|
| 902 |
lost = False
|
|
@@ -933,8 +1320,7 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 933 |
got = op(*xs)
|
| 934 |
want = ref(*xs)
|
| 935 |
if v == "overflow_expand":
|
| 936 |
-
# bounded comparison:
|
| 937 |
-
# which is where int32 flat indexing wraps
|
| 938 |
w = 1 << 20
|
| 939 |
gf, wf = got.flatten(), want.flatten()
|
| 940 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
@@ -959,20 +1345,30 @@ def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
|
| 959 |
return rows, lost
|
| 960 |
|
| 961 |
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
import
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 974 |
meta = json.load(open(sys.argv[1], "r"))
|
| 975 |
-
sys.path[:0] = meta["sys_path"]
|
| 976 |
try:
|
| 977 |
import kernels
|
| 978 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
@@ -985,85 +1381,165 @@ except Exception:
|
|
| 985 |
import load_local
|
| 986 |
_mk = load_local.load()
|
| 987 |
import torch
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 994 |
"""
|
| 995 |
|
| 996 |
|
| 997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 999 |
-
isolate_timeout_s=180, device="cuda"):
|
| 1000 |
-
"""
|
| 1001 |
-
report ok / mismatch / exception per case.
|
| 1002 |
-
|
| 1003 |
-
`make(shape, dtype)` (or `make(shape)`) builds a tensor or tuple;
|
| 1004 |
-
layout variants derive non-contiguous and misaligned views whose
|
| 1005 |
-
content equals a contiguous copy. `overflow_expand` stride-0-expands
|
| 1006 |
-
past 2**31 logical elements to catch int32 indexing (`skipped_vram`
|
| 1007 |
-
when it would not fit). Every case carries a `repro` recipe.
|
| 1008 |
-
In-process, the CUDA context is health-checked after exceptions and
|
| 1009 |
-
remaining cases marked `context_lost` if killed; `isolate=True`
|
| 1010 |
-
spawns each (shape, dtype) group in a subprocess, so a
|
| 1011 |
-
context-killing candidate costs one group (`crashed_isolated`);
|
| 1012 |
-
op/ref/make must be picklable."""
|
| 1013 |
if shapes is None:
|
| 1014 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1015 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
|
|
|
|
|
|
| 1016 |
|
| 1017 |
rows = []
|
| 1018 |
lost = False
|
| 1019 |
if isolate:
|
| 1020 |
import json
|
| 1021 |
import pickle
|
| 1022 |
-
import subprocess
|
| 1023 |
import sys as _sys
|
| 1024 |
import tempfile
|
| 1025 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1026 |
fns = os.path.join(tmpdir, "fns.pkl")
|
|
|
|
|
|
|
| 1027 |
with open(fns, "wb") as f:
|
| 1028 |
-
pickle.dump(
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
"shape": list(shape),
|
| 1041 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1042 |
"variants": list(variants),
|
| 1043 |
"ulp_tol": ulp_tol, "device": device,
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
try:
|
| 1048 |
-
r = subprocess.run(
|
| 1049 |
-
[_sys.executable, "-c", _ISOLATE_BOOTSTRAP, meta,
|
| 1050 |
-
fns, out], timeout=isolate_timeout_s,
|
| 1051 |
-
capture_output=True)
|
| 1052 |
-
if r.returncode == 0 and os.path.exists(out):
|
| 1053 |
with open(out, "rb") as f:
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
"status": "crashed_isolated",
|
| 1064 |
-
"exitcode": exitcode,
|
| 1065 |
-
"stderr_tail": stderr_tail}]
|
| 1066 |
-
rows.extend(group)
|
| 1067 |
else:
|
| 1068 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1069 |
for gi, (dtype, shape) in enumerate(pending):
|
|
@@ -1080,21 +1556,27 @@ def fuzz_shapes(op, ref, make, shapes=None, variants=_FUZZ_VARIANTS,
|
|
| 1080 |
n_bad = sum(1 for r in rows
|
| 1081 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1082 |
"skipped_empty"))
|
| 1083 |
-
|
| 1084 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
|
| 1086 |
|
| 1087 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1088 |
check=None, budget_s=None, race=False):
|
| 1089 |
-
"""Launch-configuration search
|
| 1090 |
-
bench row per grid point, sorted fastest first. A point whose factory
|
| 1091 |
-
or run raises is reported, not skipped silently. `check(fn) -> bool`
|
| 1092 |
-
is an optional correctness gate recorded per row (a fast wrong config
|
| 1093 |
-
must not win a sweep). `budget_s` caps total sweep time; points not
|
| 1094 |
-
reached are reported as `not_run` rather than silently dropped.
|
| 1095 |
-
`race=True` runs successive halving: a quick pass over every point at
|
| 1096 |
-
iters/4, then the full budget only on the fastest quartile; pruned
|
| 1097 |
-
points keep their quick measurement and status `pruned_race`."""
|
| 1098 |
keys = list(grid.keys())
|
| 1099 |
|
| 1100 |
def expand(i, acc):
|
|
@@ -1159,13 +1641,71 @@ def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
|
| 1159 |
continue
|
| 1160 |
rows.append(run_point(p, iters))
|
| 1161 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
return rows
|
| 1163 |
|
| 1164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1165 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1166 |
-
"""Device buffer for
|
| 1167 |
-
stamps.cuh): the candidate kernel writes clock64() into slot i. With
|
| 1168 |
-
`blocks`, allocates the [blocks, n] layout for MK_STAMP_BLOCK."""
|
| 1169 |
if blocks is not None:
|
| 1170 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1171 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
@@ -1175,8 +1715,7 @@ _DRIFT_SKIP = ("device", "clock", "launch_overhead", "transfer_bw")
|
|
| 1175 |
|
| 1176 |
|
| 1177 |
def pinned_dossiers():
|
| 1178 |
-
"""Names of the
|
| 1179 |
-
architecture)."""
|
| 1180 |
return sorted(_PINNED.keys())
|
| 1181 |
|
| 1182 |
|
|
@@ -1195,11 +1734,7 @@ def _numeric_leaves(d, prefix=""):
|
|
| 1195 |
|
| 1196 |
|
| 1197 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1198 |
-
"""
|
| 1199 |
-
same architecture: per-leaf relative drift, worst drift, entries
|
| 1200 |
-
over `rel_tol`. Host-dependent entries and raw clocks are skipped by
|
| 1201 |
-
default (`skip=()` compares everything); `pinned=None` auto-selects
|
| 1202 |
-
by sm_arch and raises if no pin exists."""
|
| 1203 |
if pinned is None:
|
| 1204 |
arch = current["device"]["sm_arch"]
|
| 1205 |
matches = [k for k, v in _PINNED.items()
|
|
@@ -1222,16 +1757,19 @@ def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
|
| 1222 |
"rel": round(rel, 4)})
|
| 1223 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1224 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
|
| 1229 |
|
| 1230 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1231 |
device="cuda"):
|
| 1232 |
-
"""Run the shipped MK_STAMP_BLOCK example
|
| 1233 |
-
compute, global store) and decode its stamps; a template for
|
| 1234 |
-
instrumenting a candidate."""
|
| 1235 |
props = torch.cuda.get_device_properties(device)
|
| 1236 |
if blocks is None:
|
| 1237 |
blocks = props.multi_processor_count
|
|
@@ -1244,11 +1782,7 @@ def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
|
| 1244 |
|
| 1245 |
|
| 1246 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1247 |
-
"""Decode MK_STAMP
|
| 1248 |
-
microseconds under the measured (or provided) SM clock. A 1-D buffer
|
| 1249 |
-
(MK_STAMP: block 0 only) yields one delta per phase; a 2-D [B, n]
|
| 1250 |
-
buffer (MK_STAMP_BLOCK) yields min / median / max across blocks per
|
| 1251 |
-
phase, with blocks whose stamps stayed zero excluded."""
|
| 1252 |
if clock_ghz is None:
|
| 1253 |
clock_ghz = measure_clock(stamps.device)
|
| 1254 |
|
|
@@ -1284,3 +1818,104 @@ def read_stamps(stamps, labels=None, clock_ghz=None):
|
|
| 1284 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1285 |
"blocks_reporting": int(live.sum().item()),
|
| 1286 |
"blocks_total": int(live.numel())}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Measuring instrument for the kernel-design loop: measured device facts, throttle-aware timing, adversarial correctness comparison, all as loadable ops returning plain data."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import math
|
| 3 |
import os
|
| 4 |
import time
|
|
|
|
| 8 |
try:
|
| 9 |
from ._ops import ops
|
| 10 |
except ImportError: # pragma: no cover - spawned child in a source tree
|
| 11 |
+
# children re-import the package; a source checkout rebuilds the op table from the cached JIT extension
|
|
|
|
|
|
|
| 12 |
_root = os.environ.get("METAKERNEL_DEV_ROOT")
|
| 13 |
if not _root:
|
| 14 |
raise
|
|
|
|
| 30 |
except ImportError: # pragma: no cover - fresh tree before first pin
|
| 31 |
_PINNED = {}
|
| 32 |
|
| 33 |
+
__all__ = ["probe_device", "probe_all_devices", "bench", "compare", "judge",
|
| 34 |
+
"order_shuffles", "fuzz_shapes", "sweep", "alloc_stamps",
|
| 35 |
+
"read_stamps", "stamped_demo", "measure_clock", "ulp_diff",
|
| 36 |
+
"dossier_drift", "pinned_dossiers", "mma_tile_check", "schema",
|
| 37 |
+
"probe_capturable", "ops"]
|
| 38 |
+
|
| 39 |
+
_REPO_ID = "phanerozoic/metakernel"
|
| 40 |
+
_REPO_VERSION = 1
|
| 41 |
+
_PROBE_SCHEMA = 2
|
| 42 |
+
|
| 43 |
+
# kind id, C-tile M*N, nominal K, nominal K-lanes per unrolled loop iteration
|
| 44 |
_MMA_KINDS = {
|
| 45 |
+
"fp16": (0, 16 * 16, 16, 64.0),
|
| 46 |
+
"bf16": (1, 16 * 16, 16, 64.0),
|
| 47 |
+
"tf32": (2, 16 * 16, 8, 32.0),
|
| 48 |
+
"int8": (3, 16 * 16, 16, 64.0),
|
| 49 |
+
"fp8_e4m3": (4, 16 * 8, 32, 256.0),
|
| 50 |
+
"fp16_sparse_2_4": (5, 16 * 8, 32, 128.0),
|
| 51 |
+
"fp8_e5m2": (6, 16 * 8, 32, 256.0),
|
| 52 |
}
|
| 53 |
|
| 54 |
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def _events(fn, reps):
|
| 60 |
+
"""Time reps calls of fn with one cudaEvent pair each; returns a ms list."""
|
| 61 |
starts = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 62 |
stops = [torch.cuda.Event(enable_timing=True) for _ in range(reps)]
|
| 63 |
for s, e in zip(starts, stops):
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def measure_clock(device="cuda", ticks=4_000_000, blocks=None):
|
| 72 |
+
"""Effective SM clock in GHz from clock64 ticks over wall time under an FMA spin."""
|
|
|
|
| 73 |
props = torch.cuda.get_device_properties(device)
|
| 74 |
if blocks is None:
|
| 75 |
blocks = props.multi_processor_count
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
def _sattolo_ring(n, device, generator=None):
|
| 105 |
+
"""Random single-cycle permutation as an int32 next-pointer table."""
|
|
|
|
| 106 |
perm = torch.randperm(n, generator=generator)
|
| 107 |
ring = torch.empty(n, dtype=torch.int32)
|
| 108 |
ring[perm] = torch.roll(perm, -1).to(torch.int32)
|
|
|
|
| 110 |
|
| 111 |
|
| 112 |
def _smi_settings(index):
|
| 113 |
+
"""ECC mode, driver, power limit via nvidia-smi; None fields when unavailable."""
|
|
|
|
| 114 |
import subprocess
|
| 115 |
try:
|
| 116 |
out = subprocess.run(
|
|
|
|
| 130 |
return {"ecc": None, "driver": None, "power_limit_w": None}
|
| 131 |
|
| 132 |
|
| 133 |
+
def _unravel(flat, shape):
|
| 134 |
+
"""Multi-dimensional index for a flat index into shape."""
|
| 135 |
+
if not shape:
|
| 136 |
+
return []
|
| 137 |
+
out = []
|
| 138 |
+
for s in reversed(list(shape)):
|
| 139 |
+
out.append(int(flat % s))
|
| 140 |
+
flat //= s
|
| 141 |
+
return list(reversed(out))
|
| 142 |
+
|
| 143 |
+
|
| 144 |
def mma_tile_check(device="cuda", trials=8):
|
| 145 |
+
"""Random small-integer m16n16k16 tiles through the wmma path, compared exactly to the reference GEMM."""
|
|
|
|
|
|
|
| 146 |
worst = 0.0
|
| 147 |
for t in range(trials):
|
| 148 |
g = torch.Generator(device=device).manual_seed(1000 + t)
|
|
|
|
| 160 |
"trials": trials}
|
| 161 |
|
| 162 |
|
| 163 |
+
def _tenancy_canary(device, rel_tol=0.10, attempts=3):
|
| 164 |
+
"""Fixed-cost triad timed in paired rounds; busy only when every attempt disagrees."""
|
| 165 |
+
n4 = 4 << 20
|
| 166 |
+
a = torch.rand(n4 * 4, device=device)
|
| 167 |
+
b = torch.rand(n4 * 4, device=device)
|
| 168 |
+
c = torch.empty(n4 * 4, device=device)
|
| 169 |
+
ops.mk_triad(a, b, c, 4, 1.5)
|
| 170 |
+
rels = []
|
| 171 |
+
for _ in range(attempts):
|
| 172 |
+
r1 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 173 |
+
r2 = _median(_events(lambda: ops.mk_triad(a, b, c, 4, 1.5), 3))
|
| 174 |
+
rel = abs(r1 - r2) / min(r1, r2)
|
| 175 |
+
rels.append(round(rel, 4))
|
| 176 |
+
if rel <= rel_tol:
|
| 177 |
+
return False, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 178 |
+
return True, {"rel_diffs": rels, "rel_tol": rel_tol}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _cache_dir():
|
| 182 |
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~/.cache")
|
| 183 |
+
d = os.path.join(base, "metakernel")
|
| 184 |
+
os.makedirs(d, exist_ok=True)
|
| 185 |
+
return d
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _cache_path(device, quick):
|
| 189 |
+
import re
|
| 190 |
+
props = torch.cuda.get_device_properties(device)
|
| 191 |
+
smi = _smi_settings(torch.device(device).index or 0)
|
| 192 |
+
slug = re.sub(r"[^a-z0-9]+", "", props.name.lower())[:24]
|
| 193 |
+
key = "sm%d%d_%s_drv%s_cu%s_q%d_s%d" % (
|
| 194 |
+
props.major, props.minor, slug, smi["driver"] or "na",
|
| 195 |
+
torch.version.cuda, int(quick), _PROBE_SCHEMA)
|
| 196 |
+
return os.path.join(_cache_dir(), key + ".json")
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _mma_calibrate(kind_id, sink):
|
| 200 |
+
"""Executed K-lanes per loop iteration from a sub-saturation accumulator read-back, with a linearity check."""
|
| 201 |
+
gains = []
|
| 202 |
+
for cal in (4, 32):
|
| 203 |
+
sink[1] = 0.0
|
| 204 |
+
ops.mk_mma(kind_id, 1, 1, cal, sink)
|
| 205 |
+
_sync()
|
| 206 |
+
gains.append(float(sink[1].item()) / cal)
|
| 207 |
+
linear = gains[0] > 0 and abs(gains[1] - gains[0]) / gains[0] < 0.02
|
| 208 |
+
return gains[0], linear
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _fp8_accum_stall(kind_id, sink, k_exec):
|
| 212 |
+
"""Growth curve of the fp8 accumulator; a stall reveals the effective accumulate mantissa width."""
|
| 213 |
+
vals = []
|
| 214 |
+
for it in (32, 64, 128, 256, 512):
|
| 215 |
+
sink[1] = 0.0
|
| 216 |
+
ops.mk_mma(kind_id, 1, 1, it, sink)
|
| 217 |
+
_sync()
|
| 218 |
+
vals.append(float(sink[1].item()) / 2.0)
|
| 219 |
+
if vals[-1] == vals[-2] and vals[-1] < vals[0] * 16 * 0.99:
|
| 220 |
+
bits = int(round(math.log2(vals[-1] / (2.0 * k_exec))))
|
| 221 |
+
return {"stall_element_value": vals[-1],
|
| 222 |
+
"effective_accum_mantissa_bits": bits}
|
| 223 |
+
return {"stall_element_value": None,
|
| 224 |
+
"effective_accum_mantissa_bits": None}
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def probe_device(device="cuda", quick=False, cached=True,
|
| 228 |
+
tenancy_check=True):
|
| 229 |
+
"""Empirical device dossier; every entry measured by a dedicated probe kernel and stamped with its SM clock."""
|
| 230 |
+
cpath = _cache_path(device, quick)
|
| 231 |
+
if cached and os.path.exists(cpath):
|
| 232 |
+
import json
|
| 233 |
+
return json.load(open(cpath, encoding="utf-8"))
|
| 234 |
+
if tenancy_check:
|
| 235 |
+
busy, ev = _tenancy_canary(device)
|
| 236 |
+
if busy:
|
| 237 |
+
raise RuntimeError("device_busy: canary rounds disagree %s; "
|
| 238 |
+
"idle the device or pass "
|
| 239 |
+
"tenancy_check=False" % ev)
|
| 240 |
props = torch.cuda.get_device_properties(device)
|
| 241 |
sm = props.multi_processor_count
|
| 242 |
l2 = props.L2_cache_size
|
|
|
|
| 249 |
"vram_bytes": props.total_memory,
|
| 250 |
"torch": torch.__version__,
|
| 251 |
"cuda_runtime": torch.version.cuda,
|
| 252 |
+
# instrument settings recorded because readings depend on them
|
|
|
|
| 253 |
**_smi_settings(torch.device(device).index or 0),
|
| 254 |
}
|
| 255 |
}
|
|
|
|
| 274 |
}
|
| 275 |
clk = max(traj)
|
| 276 |
|
| 277 |
+
# ---- device-memory bandwidth per access width plus read/write/gather
|
| 278 |
+
n4 = 16 << 20
|
|
|
|
| 279 |
if props.total_memory < 4 << 30:
|
| 280 |
n4 = 4 << 20
|
| 281 |
a = torch.rand(n4 * 4, device=device)
|
|
|
|
| 299 |
dtype=torch.int32).to(device)
|
| 300 |
ops.mk_gather(a, idx, c, 256, 0)
|
| 301 |
ms = _median(_events(lambda: ops.mk_gather(a, idx, c, 256, 0), reps))
|
|
|
|
| 302 |
hbm["random_gather_16B"] = round(
|
| 303 |
(idx.numel() * 4 + 2 * n4 * 16) / (ms * 1e-3) / 1e9, 1)
|
| 304 |
d["dram_bw"] = _entry(hbm, "GB/s", "streaming triad c=a+s*b per access "
|
|
|
|
| 309 |
"so its nominal number is the achievable rate for "
|
| 310 |
"random access, not a line-efficiency claim")
|
| 311 |
|
| 312 |
+
# ---- L2 bandwidth over an L2-resident working set
|
| 313 |
ws = max(l2 // 4, 1 << 20)
|
| 314 |
+
nl2 = ws // 16
|
| 315 |
al = a[:nl2 * 4].contiguous()
|
| 316 |
bl = b[:nl2 * 4].contiguous()
|
| 317 |
cl = c[:nl2 * 4].contiguous()
|
|
|
|
| 326 |
"medium", "first pass streams from HBM; amortized "
|
| 327 |
f"over {passes} passes")
|
| 328 |
|
| 329 |
+
# ---- latencies: min of 3 chases per level so a co-tenant burst cannot land in the dossier
|
|
|
|
|
|
|
| 330 |
lat = {}
|
| 331 |
idle_clk = measure_clock(device, ticks=2_000_000, blocks=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
+
def chase(op, ring, warm_hops, hops, extra=None):
|
| 334 |
+
best = None
|
| 335 |
+
for _ in range(3):
|
| 336 |
+
if extra is None:
|
| 337 |
+
op(ring, warm_hops, out64)
|
| 338 |
+
_sync()
|
| 339 |
+
op(ring, hops, out64)
|
| 340 |
+
else:
|
| 341 |
+
op(ring, extra, warm_hops, out64)
|
| 342 |
+
_sync()
|
| 343 |
+
op(ring, extra, hops, out64)
|
| 344 |
+
_sync()
|
| 345 |
+
t = int(out64[0].item()) / hops
|
| 346 |
+
best = t if best is None else min(best, t)
|
| 347 |
+
return best
|
|
|
|
|
|
|
| 348 |
|
| 349 |
+
ring_s = _sattolo_ring(8192, device)
|
| 350 |
+
lat["smem_cycles"] = round(
|
| 351 |
+
chase(lambda r, h, o: ops.mk_chase_shared(r, h, o), ring_s,
|
| 352 |
+
1 << 14, 1 << 16), 1)
|
| 353 |
+
ring_l1 = _sattolo_ring((16 << 10) // 4, device)
|
| 354 |
+
lat["l1_cycles"] = round(
|
| 355 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l1,
|
| 356 |
+
ring_l1.numel(), 1 << 15), 1)
|
| 357 |
+
ring_l2 = _sattolo_ring((2 << 20) // 4, device)
|
| 358 |
+
lat["l2_cycles"] = round(
|
| 359 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_l2,
|
| 360 |
+
ring_l2.numel(), 1 << 14), 1)
|
| 361 |
hbm_bytes = min(max(4 * l2, 64 << 20), props.total_memory // 8)
|
| 362 |
ring_h = _sattolo_ring(int(hbm_bytes) // 4, device)
|
| 363 |
hops_h = max((2 * l2) // 128, 1 << 17)
|
| 364 |
+
lat["dram_cycles"] = round(
|
| 365 |
+
chase(lambda r, h, o: ops.mk_chase_global(r, h, o), ring_h,
|
| 366 |
+
1 << 14, hops_h), 1)
|
|
|
|
|
|
|
| 367 |
lat["ns_per_cycle"] = round(1.0 / idle_clk, 3)
|
| 368 |
d["latency"] = _entry(lat, "cycles (single-lane dependent loads)",
|
| 369 |
+
"Sattolo-cycle pointer chase, min of 3 runs; 16 "
|
| 370 |
+
"KB ring for L1, 2 MB TLB-resident ring for L2, "
|
| 371 |
+
"self-evicting chain (2x L2 in touched lines) "
|
| 372 |
+
"for HBM", idle_clk, "high",
|
| 373 |
"smem includes index arithmetic; HBM includes "
|
| 374 |
"residual TLB effects at large footprints")
|
| 375 |
del ring_h, ring_l2, ring_l1, ring_s
|
| 376 |
|
| 377 |
+
# ---- fp32/fp64 FMA and tensor-core throughput
|
| 378 |
blocks = sm * 4
|
| 379 |
iters = 20_000 if quick else 60_000
|
| 380 |
ops.mk_fma_f32(blocks, 256, iters, sink)
|
| 381 |
ms = _median(_events(lambda: ops.mk_fma_f32(blocks, 256, iters, sink),
|
| 382 |
reps))
|
| 383 |
+
flops = blocks * 256 * iters * 16 * 2 * 2
|
| 384 |
d["fma_f32"] = _entry(round(flops / (ms * 1e-3) / 1e12, 2), "TFLOP/s",
|
| 385 |
"register-resident FMA chains", clk, "high")
|
| 386 |
sink64 = torch.zeros(2, dtype=torch.float64, device=device)
|
|
|
|
| 392 |
d["fma_f64"] = _entry(round(flops64 / (ms * 1e-3) / 1e12, 3), "TFLOP/s",
|
| 393 |
"register-resident double FMA chains (the DFMA "
|
| 394 |
"pipe)", clk, "high")
|
| 395 |
+
|
| 396 |
+
# ---- ILP ladder: 1/2/4 chains at low occupancy separates latency-bound from pipe-bound
|
| 397 |
+
ilp = {}
|
| 398 |
+
it_ilp = 20_000 if quick else 60_000
|
| 399 |
+
for ch in (1, 2, 4):
|
| 400 |
+
ops.mk_fma_ilp(ch, sm, 64, it_ilp, sink)
|
| 401 |
+
ms = _median(_events(
|
| 402 |
+
lambda c2=ch: ops.mk_fma_ilp(c2, sm, 64, it_ilp, sink), reps))
|
| 403 |
+
ilp[f"ilp{ch}"] = round(
|
| 404 |
+
sm * 64 * it_ilp * 16 * 2 * ch / (ms * 1e-3) / 1e12, 2)
|
| 405 |
+
ilp["ratio_4_1"] = round(ilp["ilp4"] / ilp["ilp1"], 2)
|
| 406 |
+
d["fma_ilp"] = _entry(ilp, "TFLOP/s",
|
| 407 |
+
"1/2/4 independent dependent FMA chains per "
|
| 408 |
+
"thread at one block of 64 threads per SM; a "
|
| 409 |
+
"ratio near 4 means latency-bound at that "
|
| 410 |
+
"occupancy", clk, "high")
|
| 411 |
+
|
| 412 |
mma = {}
|
| 413 |
mma_klanes = {}
|
| 414 |
+
fp8_accum = {}
|
| 415 |
+
for name, (kind, mn, k_nom, nom_lanes) in _MMA_KINDS.items():
|
| 416 |
it = 30_000 if quick else 100_000
|
| 417 |
r = ops.mk_mma(kind, blocks, 8, 512, sink)
|
| 418 |
if r != 0:
|
| 419 |
mma[name] = "unsupported"
|
| 420 |
continue
|
| 421 |
_sync()
|
| 422 |
+
gain, linear = _mma_calibrate(kind, sink)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
mma_klanes[name] = {
|
| 424 |
+
"executed_k_per_iter": round(gain, 2),
|
| 425 |
+
"vs_nominal": round(gain / nom_lanes, 3),
|
| 426 |
+
"calibration_linear": linear,
|
| 427 |
}
|
| 428 |
+
if name.startswith("fp8"):
|
| 429 |
+
fp8_accum[name] = _fp8_accum_stall(kind, sink, k_nom)
|
| 430 |
ms = _median(_events(
|
| 431 |
lambda k=kind, i=it: ops.mk_mma(k, blocks, 8, i, sink), reps))
|
| 432 |
+
tflops = blocks * 8 * it * 2.0 * mn * gain / (ms * 1e-3) / 1e12
|
| 433 |
+
if name == "fp16_sparse_2_4":
|
| 434 |
+
mma[name] = {"executed": round(tflops, 1),
|
| 435 |
+
"dense_equivalent": round(2 * tflops, 1)}
|
| 436 |
+
else:
|
| 437 |
+
mma[name] = round(tflops, 1)
|
| 438 |
d["mma"] = _entry(mma, "TFLOP/s (TOP/s for int8)",
|
| 439 |
"register-fragment mma_sync loops (wmma; fp8 and "
|
| 440 |
+
"2:4-sparse fp16 via inline PTX m16n8k32); flops "
|
| 441 |
+
"derive from a sub-saturation accumulator read-back "
|
|
|
|
| 442 |
"with every fragment element observed, never the "
|
| 443 |
+
"declared loop shape; sparse reports executed and "
|
| 444 |
+
"dense-equivalent flops",
|
| 445 |
clk, "medium",
|
| 446 |
"dense-tile rate without memory traffic; real "
|
| 447 |
"kernels see this only when staging hides behind it")
|
| 448 |
d["mma"]["verified_k_lanes"] = mma_klanes
|
| 449 |
+
if fp8_accum:
|
| 450 |
+
d["mma"]["fp8_accum"] = fp8_accum
|
| 451 |
|
| 452 |
+
# ---- fed MMA: smem-staged fp16 tiles under cp.async double buffering
|
| 453 |
+
if props.major >= 8:
|
| 454 |
+
gbuf = torch.ones(16 << 20, dtype=torch.float16, device=device)
|
| 455 |
+
sink[1] = 0.0
|
| 456 |
+
r = ops.mk_mma_fed(gbuf, 1, 1, 64, sink)
|
| 457 |
+
if r == 0:
|
| 458 |
+
_sync()
|
| 459 |
+
fed_gain = float(sink[1].item()) / 64
|
| 460 |
+
it_fed = 6_000 if quick else 20_000
|
| 461 |
+
ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink)
|
| 462 |
+
ms = _median(_events(
|
| 463 |
+
lambda: ops.mk_mma_fed(gbuf, sm * 2, 8, it_fed, sink),
|
| 464 |
+
reps))
|
| 465 |
+
fed = sm * 2 * 8 * it_fed * 2.0 * 256 * fed_gain / \
|
| 466 |
+
(ms * 1e-3) / 1e12
|
| 467 |
+
d["mma_fed"] = _entry(
|
| 468 |
+
round(fed, 1), "TFLOP/s",
|
| 469 |
+
"m16n16k16 fp16 tiles fed from an L2-resident buffer "
|
| 470 |
+
"through cp.async double-buffered shared memory; "
|
| 471 |
+
"accumulator-verified", clk, "high",
|
| 472 |
+
"the achieved counterpart to the register peak: staging "
|
| 473 |
+
"exposed, one dependent accumulator chain per warp")
|
| 474 |
+
del gbuf
|
| 475 |
+
|
| 476 |
+
# ---- shared-memory bandwidth and the bank-conflict stride sweep
|
| 477 |
it_sm = 30_000 if quick else 100_000
|
| 478 |
ops.mk_smem_bw(blocks, 256, it_sm, sink)
|
| 479 |
ms = _median(_events(lambda: ops.mk_smem_bw(blocks, 256, it_sm, sink),
|
|
|
|
| 483 |
"block-resident read+write streaming (8 B per "
|
| 484 |
"thread-iteration), aggregated across SMs", clk,
|
| 485 |
"medium",
|
| 486 |
+
"bank-conflict-light access pattern; the stride "
|
| 487 |
+
"sweep below prices the conflicted case")
|
| 488 |
+
strides = (1, 8, 32) if quick else (1, 2, 4, 8, 16, 32)
|
| 489 |
+
it_st = 8_000 if quick else 20_000
|
| 490 |
+
st_ms = {}
|
| 491 |
+
for s2 in strides:
|
| 492 |
+
ops.mk_smem_stride(s2, blocks, 256, it_st, sink)
|
| 493 |
+
st_ms[s2] = _median(_events(
|
| 494 |
+
lambda s3=s2: ops.mk_smem_stride(s3, blocks, 256, it_st, sink),
|
| 495 |
+
reps))
|
| 496 |
+
d["smem_conflict_penalty"] = _entry(
|
| 497 |
+
{f"stride_{s2}": round(st_ms[s2] / st_ms[1], 2) for s2 in strides},
|
| 498 |
+
"x (time ratio vs stride 1)",
|
| 499 |
+
"dependent read-FMA-write per thread at warp-lane bank distance "
|
| 500 |
+
"`stride`; stride 32 puts every lane in one bank", clk, "high",
|
| 501 |
+
"the direct conflict measurement that disambiguates a "
|
| 502 |
+
"bank-conflict signature from an ILP stall")
|
| 503 |
|
| 504 |
# ---- PCIe / host transfer bandwidth
|
| 505 |
nbytes = 256 << 20
|
|
|
|
| 530 |
"d2d counts one direction of traffic")
|
| 531 |
del host_pin, host_pag, dev_t, dev_u
|
| 532 |
|
| 533 |
+
# ---- atomic contention curve
|
|
|
|
|
|
|
| 534 |
n_thr = blocks * 256
|
| 535 |
buf = torch.zeros(n_thr, dtype=torch.float32, device=device)
|
| 536 |
at = {"curve_slots": {}}
|
|
|
|
| 540 |
buf.zero_()
|
| 541 |
ops.mk_atomics(1, blocks, 256, it_at, buf, slots)
|
| 542 |
ms = _median(_events(
|
| 543 |
+
lambda s4=slots: ops.mk_atomics(1, blocks, 256, it_at, buf, s4),
|
| 544 |
reps))
|
| 545 |
at["curve_slots"][str(slots)] = round(
|
| 546 |
n_thr * it_at / (ms * 1e-3) / 1e9, 2)
|
|
|
|
| 553 |
"of distinct global addresses from 1 (full "
|
| 554 |
"contention) to one per thread", clk, "high")
|
| 555 |
|
| 556 |
+
# ---- launch overhead: torch-dispatch path and the raw C++ path
|
| 557 |
anchor = sink
|
| 558 |
n_launch = 500 if quick else 2000
|
| 559 |
_sync()
|
|
|
|
| 568 |
ops.mk_empty(anchor)
|
| 569 |
_sync()
|
| 570 |
rt = (time.perf_counter() - t0) / n_rt * 1e6
|
| 571 |
+
raw_q = float(ops.mk_launch_raw(n_launch, 0, anchor))
|
| 572 |
+
raw_rt = float(ops.mk_launch_raw(n_rt, 1, anchor))
|
| 573 |
d["launch_overhead"] = _entry(
|
| 574 |
+
{"torch_queued_us": round(queued, 2),
|
| 575 |
+
"torch_round_trip_us": round(rt, 2),
|
| 576 |
+
"raw_queued_us": round(raw_q / n_launch * 1e6, 2)
|
| 577 |
+
if raw_q >= 0 else None,
|
| 578 |
+
"raw_round_trip_us": round(raw_rt / n_rt * 1e6, 2)
|
| 579 |
+
if raw_rt >= 0 else None},
|
| 580 |
+
"us", "empty-kernel trains; torch path per-launch through op "
|
| 581 |
+
"dispatch, raw path looped inside one binding call", clk,
|
| 582 |
+
"medium", "host-state-dependent; the torch-minus-raw gap is the "
|
| 583 |
+
"dispatch cost a torch-launched candidate pays")
|
| 584 |
|
| 585 |
# ---- grid barrier
|
| 586 |
bar_iters = 500 if quick else 2000
|
|
|
|
| 596 |
"unavailable or grid too large", clk,
|
| 597 |
"high")
|
| 598 |
|
| 599 |
+
# ---- occupancy API table plus timed knees
|
|
|
|
|
|
|
| 600 |
occ = {}
|
| 601 |
for threads in (128, 256, 512, 1024):
|
| 602 |
row = {}
|
|
|
|
| 617 |
ops.mk_fma_dep(knee_blocks, threads, smem_kb * 1024,
|
| 618 |
knee_iters, sink)
|
| 619 |
ms = _median(_events(
|
| 620 |
+
lambda t=threads, s5=smem_kb: ops.mk_fma_dep(
|
| 621 |
+
knee_blocks, t, s5 * 1024, knee_iters, sink), reps))
|
| 622 |
fl = knee_blocks * threads * knee_iters * 16 * 2
|
| 623 |
row[f"{smem_kb}KB"] = round(fl / (ms * 1e-3) / 1e12, 2)
|
| 624 |
knees[f"t{threads}"] = row
|
|
|
|
| 627 |
"at forced (threads, dyn smem) occupancies over fixed total work; "
|
| 628 |
"the cliff marks where occupancy stops covering latency",
|
| 629 |
clk, "high", "timing-derived counterpart to the API table above")
|
| 630 |
+
if cached:
|
| 631 |
+
import json
|
| 632 |
+
json.dump(d, open(cpath, "w", encoding="utf-8"))
|
| 633 |
return d
|
| 634 |
|
| 635 |
|
| 636 |
+
def probe_all_devices(quick=True, cached=True, tenancy_check=True):
|
| 637 |
+
"""One dossier per visible device plus a pairwise P2P bandwidth matrix."""
|
| 638 |
+
n = torch.cuda.device_count()
|
| 639 |
+
devices = {}
|
| 640 |
+
for i in range(n):
|
| 641 |
+
devices[i] = probe_device(f"cuda:{i}", quick=quick, cached=cached,
|
| 642 |
+
tenancy_check=tenancy_check)
|
| 643 |
+
p2p = {}
|
| 644 |
+
if n > 1:
|
| 645 |
+
nbytes = 64 << 20
|
| 646 |
+
for i in range(n):
|
| 647 |
+
for j in range(n):
|
| 648 |
+
if i == j:
|
| 649 |
+
continue
|
| 650 |
+
src = torch.rand(nbytes // 4, device=f"cuda:{i}")
|
| 651 |
+
dst = torch.empty(nbytes // 4, device=f"cuda:{j}")
|
| 652 |
+
dst.copy_(src)
|
| 653 |
+
torch.cuda.synchronize(i)
|
| 654 |
+
torch.cuda.synchronize(j)
|
| 655 |
+
t0 = time.perf_counter()
|
| 656 |
+
for _ in range(5):
|
| 657 |
+
dst.copy_(src)
|
| 658 |
+
torch.cuda.synchronize(i)
|
| 659 |
+
torch.cuda.synchronize(j)
|
| 660 |
+
ms = (time.perf_counter() - t0) / 5 * 1e3
|
| 661 |
+
p2p[f"{i}->{j}"] = {
|
| 662 |
+
"gbs": round(nbytes / (ms * 1e-3) / 1e9, 1),
|
| 663 |
+
"peer_access": bool(
|
| 664 |
+
torch.cuda.can_device_access_peer(i, j)),
|
| 665 |
+
}
|
| 666 |
+
del src, dst
|
| 667 |
+
return {"devices": devices, "p2p": p2p}
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
_CAPTURE_PROBE = r"""
|
| 671 |
+
import pickle, sys, torch
|
| 672 |
+
fn = pickle.load(open(sys.argv[1], "rb"))
|
| 673 |
+
for _ in range(3):
|
| 674 |
+
fn()
|
| 675 |
+
torch.cuda.synchronize()
|
| 676 |
+
try:
|
| 677 |
+
g = torch.cuda.CUDAGraph()
|
| 678 |
+
with torch.cuda.graph(g):
|
| 679 |
+
fn()
|
| 680 |
+
print("capturable", flush=True)
|
| 681 |
+
except Exception as e:
|
| 682 |
+
print("uncapturable: %s: %s" % (type(e).__name__, str(e)[:120]),
|
| 683 |
+
flush=True)
|
| 684 |
+
"""
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def probe_capturable(fn, timeout_s=180):
|
| 688 |
+
"""Test CUDA-graph capturability of fn in a child process, so a capture-wedged RNG never reaches the caller."""
|
| 689 |
+
import pickle
|
| 690 |
+
import subprocess
|
| 691 |
+
import sys as _sys
|
| 692 |
+
import tempfile
|
| 693 |
+
try:
|
| 694 |
+
blob = pickle.dumps(fn)
|
| 695 |
+
except Exception: # noqa: BLE001
|
| 696 |
+
return {"capturable": None,
|
| 697 |
+
"reason": "fn is not picklable; probe skipped (define fn "
|
| 698 |
+
"at module top level to enable it)"}
|
| 699 |
+
path = os.path.join(tempfile.mkdtemp(prefix="mk_cap_"), "fn.pkl")
|
| 700 |
+
with open(path, "wb") as f:
|
| 701 |
+
f.write(blob)
|
| 702 |
+
try:
|
| 703 |
+
r = subprocess.run([_sys.executable, "-c", _CAPTURE_PROBE, path],
|
| 704 |
+
capture_output=True, text=True,
|
| 705 |
+
timeout=timeout_s)
|
| 706 |
+
except Exception as e: # noqa: BLE001
|
| 707 |
+
return {"capturable": None,
|
| 708 |
+
"reason": f"probe child failed: {type(e).__name__}"}
|
| 709 |
+
out = (r.stdout or "").strip()
|
| 710 |
+
if out == "capturable":
|
| 711 |
+
return {"capturable": True, "reason": ""}
|
| 712 |
+
if out.startswith("uncapturable"):
|
| 713 |
+
return {"capturable": False, "reason": out}
|
| 714 |
+
return {"capturable": None,
|
| 715 |
+
"reason": "probe child exit %s: %s" % (
|
| 716 |
+
r.returncode, (r.stderr or "")[-200:])}
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
def _bench_verdict(rep):
|
| 720 |
+
parts = ["median %.4g ms" % rep["median_ms"]]
|
| 721 |
+
if "achieved_tflops" in rep:
|
| 722 |
+
parts.append("%.4g TFLOP/s" % rep["achieved_tflops"])
|
| 723 |
+
elif "achieved_gbs" in rep:
|
| 724 |
+
parts.append("%.4g GB/s" % rep["achieved_gbs"])
|
| 725 |
+
if "roofline" in rep:
|
| 726 |
+
parts.append("%.0f%% of %s roof" % (rep["roofline"]["pct_of_roof"],
|
| 727 |
+
rep["roofline"]["bound"]))
|
| 728 |
+
if rep["rejected_throttled"]:
|
| 729 |
+
parts.append("%d/%d rejected (%s)" % (
|
| 730 |
+
rep["rejected_throttled"], rep["samples"],
|
| 731 |
+
rep["rejection_basis"]))
|
| 732 |
+
if rep.get("no_clean_window"):
|
| 733 |
+
parts.append("no clean window: the whole run was throttled")
|
| 734 |
+
if rep.get("alloc_growth_bytes_per_call"):
|
| 735 |
+
parts.append("fn allocates %d B/call" %
|
| 736 |
+
rep["alloc_growth_bytes_per_call"])
|
| 737 |
+
if rep.get("host_overhead_suspected"):
|
| 738 |
+
parts.append("fn appears to sync or do host work inside")
|
| 739 |
+
return "; ".join(parts)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
def bench(fn, iters=100, warmup=10, bytes=None, flops=None, dossier=None,
|
| 743 |
dtype="fp16", clock_guard=0.97, clock_stride=8, graph=False,
|
| 744 |
+
graph_probe_isolated=False, device="cuda"):
|
| 745 |
+
"""Median-of-N cudaEvent timing with throttle rejection, roofline placement, and footgun detection."""
|
| 746 |
+
for name, v in (("iters", iters), ("warmup", warmup)):
|
| 747 |
+
if not isinstance(v, int) or v < (1 if name == "iters" else 0):
|
| 748 |
+
raise ValueError(f"{name} must be a non-negative int; "
|
| 749 |
+
f"got {v!r}")
|
| 750 |
+
for name, v in (("bytes", bytes), ("flops", flops)):
|
| 751 |
+
if v is not None and (not isinstance(v, (int, float)) or v <= 0):
|
| 752 |
+
raise ValueError(f"{name} must be a positive number counting "
|
| 753 |
+
f"{name} per call of fn; got {v!r}")
|
|
|
|
| 754 |
for _ in range(warmup):
|
| 755 |
fn()
|
| 756 |
_sync()
|
| 757 |
+
# footgun probes: per-call allocation growth and hidden host work
|
| 758 |
+
alloc0 = torch.cuda.memory_allocated(device)
|
| 759 |
+
fn()
|
| 760 |
+
fn()
|
| 761 |
+
_sync()
|
| 762 |
+
alloc_growth = max(0, (torch.cuda.memory_allocated(device) - alloc0)
|
| 763 |
+
// 2)
|
| 764 |
+
# a burst of enqueues that leaves the stream idle means fn drains it; only trustworthy above the launch-overhead floor
|
| 765 |
+
for _ in range(16):
|
| 766 |
+
fn()
|
| 767 |
+
stream_idle = bool(torch.cuda.current_stream(device).query())
|
| 768 |
+
_sync()
|
| 769 |
+
t0 = time.perf_counter()
|
| 770 |
+
pre = _events(fn, 4)
|
| 771 |
+
wall_ms = (time.perf_counter() - t0) * 1e3
|
| 772 |
+
ev_ms = sum(pre)
|
| 773 |
+
host_suspect = (stream_idle and ev_ms / 4 > 0.05) or \
|
| 774 |
+
wall_ms > ev_ms * 1.5 + 2.0
|
| 775 |
+
|
| 776 |
graph_state = "off"
|
| 777 |
graph_reason = ""
|
| 778 |
+
if graph and graph_probe_isolated:
|
| 779 |
+
cap = probe_capturable(fn)
|
| 780 |
+
if cap["capturable"] is False:
|
| 781 |
+
graph = False
|
| 782 |
+
graph_state = "fallback_eager"
|
| 783 |
+
graph_reason = "isolated probe: " + cap["reason"]
|
| 784 |
if graph:
|
| 785 |
pre_rng = torch.cuda.get_rng_state(device)
|
| 786 |
try:
|
|
|
|
| 795 |
except Exception as e: # noqa: BLE001 - fall back, say why
|
| 796 |
graph_state = "fallback_eager"
|
| 797 |
graph_reason = f"{type(e).__name__}: {str(e)[:120]}"
|
| 798 |
+
# drain the invalidated stream; a failed capture can wedge the device RNG on some torch versions
|
|
|
|
|
|
|
|
|
|
| 799 |
rng_ok = True
|
| 800 |
for _attempt in range(5):
|
| 801 |
try:
|
|
|
|
| 825 |
samples.append((_events(fn, n), len(clocks) - 1))
|
| 826 |
clocks.append(measure_clock(device, ticks=probe_ticks))
|
| 827 |
best_clk = max(clocks)
|
| 828 |
+
|
| 829 |
+
# rejection basis: memory-bound work is judged on its own rate, not the SM clock
|
| 830 |
+
ridge_memory = False
|
| 831 |
+
if dossier is not None and bytes and flops:
|
| 832 |
+
bw0 = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 833 |
+
mma_v0 = dossier["mma"]["value"].get(dtype)
|
| 834 |
+
if isinstance(mma_v0, dict):
|
| 835 |
+
mma_v0 = mma_v0.get("dense_equivalent")
|
| 836 |
+
peak0 = (mma_v0 if isinstance(mma_v0, (int, float))
|
| 837 |
+
else dossier["fma_f32"]["value"]) * 1e12
|
| 838 |
+
ridge_memory = (flops / bytes) * bw0 < peak0
|
| 839 |
+
basis = "bandwidth_consistency" if (
|
| 840 |
+
bytes is not None and (flops is None or ridge_memory)) \
|
| 841 |
+
else "sm_clock"
|
| 842 |
+
|
| 843 |
flat = []
|
| 844 |
+
kept = []
|
| 845 |
+
rejected = 0
|
| 846 |
+
if basis == "sm_clock":
|
| 847 |
+
for group, ci in samples:
|
| 848 |
+
window_ok = (clocks[ci] >= clock_guard * best_clk and
|
| 849 |
+
clocks[min(ci + 1, len(clocks) - 1)] >=
|
| 850 |
+
clock_guard * best_clk)
|
| 851 |
+
for ms in group:
|
| 852 |
+
flat.append(ms)
|
| 853 |
+
if window_ok:
|
| 854 |
+
kept.append(ms)
|
| 855 |
+
else:
|
| 856 |
+
rejected += 1
|
| 857 |
+
else:
|
| 858 |
+
for group, _ci in samples:
|
| 859 |
+
flat.extend(group)
|
| 860 |
+
floor = sorted(flat)[max(0, int(0.05 * len(flat)) - 1)]
|
| 861 |
+
for ms in flat:
|
| 862 |
+
if ms <= floor * 1.15:
|
| 863 |
kept.append(ms)
|
| 864 |
else:
|
| 865 |
rejected += 1
|
| 866 |
+
all_rejected = not kept
|
| 867 |
+
if all_rejected:
|
| 868 |
kept = flat
|
| 869 |
med = _median(kept)
|
| 870 |
ks = sorted(kept)
|
| 871 |
n_k = len(ks)
|
| 872 |
+
# order-statistic 95% CI on the median; degenerate at tiny n
|
|
|
|
| 873 |
half = 1.959964 * (n_k ** 0.5) / 2.0
|
| 874 |
lo_i = max(0, int(n_k / 2 - half) - 1)
|
| 875 |
hi_i = min(n_k - 1, int(n_k / 2 + half))
|
|
|
|
| 884 |
"mean_ms": round(sum(kept) / n_k, 4),
|
| 885 |
"samples": len(flat),
|
| 886 |
"rejected_throttled": rejected,
|
| 887 |
+
"rejection_basis": basis,
|
| 888 |
"graph_captured": graph_state == "captured",
|
| 889 |
"clock_ghz": {"best": round(best_clk, 3),
|
| 890 |
"worst": round(min(clocks), 3)},
|
| 891 |
}
|
| 892 |
+
no_clean = all_rejected
|
| 893 |
+
if basis == "sm_clock" and dossier is not None:
|
| 894 |
+
ref_clk = dossier["clock"]["max_ghz"]
|
| 895 |
+
if best_clk < clock_guard * ref_clk:
|
| 896 |
+
no_clean = True
|
| 897 |
+
if no_clean:
|
| 898 |
+
rep["no_clean_window"] = True
|
| 899 |
if graph_reason:
|
| 900 |
rep["graph_fallback_reason"] = graph_reason
|
| 901 |
+
if alloc_growth:
|
| 902 |
+
rep["alloc_growth_bytes_per_call"] = int(alloc_growth)
|
| 903 |
+
if host_suspect:
|
| 904 |
+
rep["host_overhead_suspected"] = True
|
| 905 |
+
rep["host_wall_over_event"] = round(wall_ms / max(ev_ms, 1e-9), 2)
|
| 906 |
if bytes is not None:
|
| 907 |
rep["achieved_gbs"] = round(bytes / (med * 1e-3) / 1e9, 1)
|
| 908 |
if flops is not None:
|
|
|
|
| 912 |
if dossier is not None and bytes and flops:
|
| 913 |
bw = max(dossier["dram_bw"]["value"].values()) * 1e9
|
| 914 |
mma_v = dossier["mma"]["value"].get(dtype)
|
| 915 |
+
if isinstance(mma_v, dict):
|
| 916 |
+
mma_v = mma_v.get("dense_equivalent")
|
| 917 |
peak = (mma_v if isinstance(mma_v, (int, float))
|
| 918 |
else dossier["fma_f32"]["value"]) * 1e12
|
| 919 |
ai = flops / bytes
|
|
|
|
| 924 |
"pct_of_roof": round(100 * (flops / (med * 1e-3)) / roof, 1),
|
| 925 |
"peak_dtype": dtype,
|
| 926 |
}
|
| 927 |
+
rep["verdict"] = _bench_verdict(rep)
|
| 928 |
return rep
|
| 929 |
|
| 930 |
|
| 931 |
def _to_ordered_int(t):
|
| 932 |
+
"""Monotone integer mapping of float bit patterns; differences are ULP distances."""
|
|
|
|
|
|
|
| 933 |
if t.dtype == torch.float32:
|
| 934 |
u = t.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 935 |
half = 1 << 31
|
|
|
|
| 945 |
|
| 946 |
|
| 947 |
def ulp_diff(a, b):
|
| 948 |
+
"""Elementwise ULP distance; both-NaN counts 0, one-sided NaN 2**62, integers by absolute difference."""
|
| 949 |
+
if a.dtype != b.dtype or a.shape != b.shape:
|
| 950 |
+
raise ValueError("ulp_diff needs matching dtype and shape; got "
|
| 951 |
+
f"{a.dtype}{tuple(a.shape)} vs "
|
| 952 |
+
f"{b.dtype}{tuple(b.shape)}; cast or reshape the "
|
| 953 |
+
"candidate to the reference layout")
|
| 954 |
if not a.is_floating_point():
|
| 955 |
return (a.to(torch.int64) - b.to(torch.int64)).abs()
|
| 956 |
ia, ib = _to_ordered_int(a), _to_ordered_int(b)
|
|
|
|
| 967 |
|
| 968 |
@staticmethod
|
| 969 |
def last_dim():
|
| 970 |
+
"""Identical permutation of every arg's last dim: reduction order changes, value does not."""
|
|
|
|
|
|
|
| 971 |
def shuffle(args, seed):
|
| 972 |
g = torch.Generator(device=args[0].device).manual_seed(seed)
|
| 973 |
p = torch.randperm(args[0].shape[-1], device=args[0].device,
|
|
|
|
| 977 |
|
| 978 |
@staticmethod
|
| 979 |
def matmul_k():
|
| 980 |
+
"""Consistent K permutation of (A [.., M, K], B [.., K, N]): product unchanged, accumulation order not."""
|
|
|
|
| 981 |
def shuffle(args, seed):
|
| 982 |
a_m, b_m = args[0], args[1]
|
| 983 |
g = torch.Generator(device=a_m.device).manual_seed(seed)
|
|
|
|
| 991 |
return list(x) if isinstance(x, (tuple, list)) else [x]
|
| 992 |
|
| 993 |
|
| 994 |
+
def _compare_hints(trial):
|
| 995 |
+
hints = []
|
| 996 |
+
if trial.get("bias_one_sided_frac", 0) >= 0.9 and \
|
| 997 |
+
trial.get("frac_above_band", 0) > 0.5:
|
| 998 |
+
hints.append("systematic one-sided bias: check scale factors or "
|
| 999 |
+
"the accumulator dtype")
|
| 1000 |
+
if trial["nan_cand"] > trial["nan_ref"]:
|
| 1001 |
+
hints.append("candidate introduces NaN: check uninitialized "
|
| 1002 |
+
"memory or masking")
|
| 1003 |
+
nd = trial.get("first_divergent_nd")
|
| 1004 |
+
if nd and nd[-1] > 0 and any(nd[-1] % t == 0 for t in (16, 32, 64,
|
| 1005 |
+
128)):
|
| 1006 |
+
hints.append("divergence starts on a tile-aligned index: check "
|
| 1007 |
+
"boundary indexing")
|
| 1008 |
+
return hints
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def compare(ref, cand, gen=None, trials=8, order_shuffle=None,
|
| 1012 |
+
order_trials=8, ref_variants=None, ulp_tol=None, atol=None,
|
| 1013 |
+
rtol=None, band_margin=4.0, zero_atol=None, worst_k=8, seed=0,
|
| 1014 |
+
inputs=None, frac_above_tol=0.5):
|
| 1015 |
+
"""Adversarial correctness comparison with pointwise order bands, ULP evidence, and a one-sided-bias gate; the banded route cannot reject errors under band_margin*(band+2) ULP at order-stable elements."""
|
| 1016 |
+
if (gen is None) == (inputs is None):
|
| 1017 |
+
raise ValueError("pass exactly one of gen (a seed -> args "
|
| 1018 |
+
"callable) or inputs (a tuple of concrete "
|
| 1019 |
+
"tensors)")
|
| 1020 |
+
if inputs is not None:
|
| 1021 |
+
if isinstance(inputs, torch.Tensor):
|
| 1022 |
+
inputs = (inputs,)
|
| 1023 |
+
if not all(isinstance(x, torch.Tensor) for x in inputs):
|
| 1024 |
+
raise ValueError("inputs must be tensors; wrap scalars with "
|
| 1025 |
+
"torch.tensor first")
|
| 1026 |
+
fixed = tuple(inputs)
|
| 1027 |
+
gen = lambda seed: fixed # noqa: E731
|
| 1028 |
+
trials = 1
|
| 1029 |
worst = {"max_ulp": -1}
|
| 1030 |
total_hist = {}
|
| 1031 |
per_trial = []
|
| 1032 |
max_abs_seen = 0.0
|
| 1033 |
max_rel_seen = 0.0
|
| 1034 |
+
out_shapes = []
|
| 1035 |
for t in range(trials):
|
| 1036 |
args = gen(seed + t)
|
| 1037 |
+
if not isinstance(args, (tuple, list)):
|
| 1038 |
+
raise ValueError("gen(seed) must return a tuple of args; got "
|
| 1039 |
+
f"{type(args).__name__}; return (x,) for a "
|
| 1040 |
+
"single-argument op")
|
| 1041 |
r_outs = _as_outputs(ref(*args))
|
| 1042 |
c_outs = _as_outputs(cand(*args))
|
| 1043 |
+
if len(r_outs) != len(c_outs):
|
| 1044 |
+
raise ValueError(f"reference returned {len(r_outs)} outputs, "
|
| 1045 |
+
f"candidate {len(c_outs)}; make the arities "
|
| 1046 |
+
"match")
|
| 1047 |
+
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
| 1048 |
+
if r.dtype != c.dtype:
|
| 1049 |
+
raise ValueError(f"output {oi} dtype mismatch: ref "
|
| 1050 |
+
f"{r.dtype}, cand {c.dtype}; cast the "
|
| 1051 |
+
"candidate to the reference dtype")
|
| 1052 |
+
if r.shape != c.shape:
|
| 1053 |
+
raise ValueError(f"output {oi} shape mismatch: ref "
|
| 1054 |
+
f"{tuple(r.shape)}, cand "
|
| 1055 |
+
f"{tuple(c.shape)}")
|
| 1056 |
+
out_shapes = [tuple(r.shape) for r in r_outs]
|
| 1057 |
+
# pointwise band: each element judged against its own measured order sensitivity
|
| 1058 |
bands = None
|
| 1059 |
band = 0
|
| 1060 |
band_score = 0.0
|
|
|
|
| 1078 |
max_ulp = 0
|
| 1079 |
mean_ulp = 0.0
|
| 1080 |
first_div = -1
|
| 1081 |
+
first_div_nd = None
|
| 1082 |
n_el = 0
|
| 1083 |
n_above_band = 0
|
| 1084 |
+
n_diverging = 0
|
| 1085 |
+
n_cand_high = 0
|
| 1086 |
nan_r = nan_c = inf_r = inf_c = 0
|
| 1087 |
worst_elements = []
|
| 1088 |
for oi, (r, c) in enumerate(zip(r_outs, c_outs)):
|
|
|
|
| 1100 |
mean_ulp += float(d.double().sum().item())
|
| 1101 |
n_el += d.numel()
|
| 1102 |
nz = (d > 0)
|
| 1103 |
+
n_diverging += int(nz.sum().item())
|
| 1104 |
+
n_cand_high += int(((c > r) & nz).sum().item())
|
| 1105 |
if first_div < 0 and nz.any():
|
| 1106 |
first_div = int(nz.flatten().nonzero()[0].item())
|
| 1107 |
+
first_div_nd = _unravel(first_div, r.shape)
|
| 1108 |
if nz.any():
|
| 1109 |
buckets = torch.clamp(
|
| 1110 |
torch.log2(d[nz].double()).floor(), 0, 62
|
|
|
|
| 1121 |
if val > 0:
|
| 1122 |
worst_elements.append({
|
| 1123 |
"output": oi, "index": int(ix),
|
| 1124 |
+
"index_nd": _unravel(int(ix), r.shape),
|
| 1125 |
"ref": float(rf[ix].item()),
|
| 1126 |
"cand": float(cf[ix].item()),
|
| 1127 |
"ulp": int(val)})
|
|
|
|
| 1141 |
inf_r += int(torch.isinf(r).sum().item())
|
| 1142 |
inf_c += int(torch.isinf(c).sum().item())
|
| 1143 |
worst_elements.sort(key=lambda w: -w["ulp"])
|
| 1144 |
+
frac_pos = n_cand_high / max(n_diverging, 1)
|
| 1145 |
trial_rep = {
|
| 1146 |
"max_ulp": max_ulp,
|
| 1147 |
"mean_ulp": round(mean_ulp / max(n_el, 1), 3),
|
| 1148 |
"first_divergent_index": first_div,
|
| 1149 |
+
"first_divergent_nd": first_div_nd,
|
| 1150 |
"nan_ref": nan_r, "nan_cand": nan_c,
|
| 1151 |
"inf_ref": inf_r, "inf_cand": inf_c,
|
| 1152 |
"order_band_ulp": band,
|
| 1153 |
"band_score": round(band_score, 2),
|
| 1154 |
"frac_above_band": round(n_above_band / max(n_el, 1), 6),
|
| 1155 |
+
"bias_one_sided_frac": round(max(frac_pos, 1 - frac_pos), 4)
|
| 1156 |
+
if n_diverging else 0.0,
|
| 1157 |
"worst_elements": worst_elements[:worst_k],
|
| 1158 |
}
|
| 1159 |
per_trial.append(trial_rep)
|
| 1160 |
if max_ulp > worst["max_ulp"]:
|
| 1161 |
worst = trial_rep
|
|
|
|
| 1162 |
max_seen = max(t["max_ulp"] for t in per_trial)
|
| 1163 |
+
banded = order_shuffle is not None or ref_variants
|
| 1164 |
+
if banded:
|
| 1165 |
score = max(t["band_score"] for t in per_trial)
|
| 1166 |
ulp_ok = score <= band_margin
|
| 1167 |
criterion = (f"pointwise max(ulp / (order_band + 2)) = "
|
|
|
|
| 1176 |
max_rel_seen <= (rtol or 0) or max_abs_seen <= (atol or 0)
|
| 1177 |
criterion += (f"; OR max_abs {max_abs_seen:.3e} <= atol {atol} / "
|
| 1178 |
f"max_rel {max_rel_seen:.3e} <= rtol {rtol}")
|
| 1179 |
+
# one-sided bias gate: a uniform small error is above-band nearly everywhere in one direction
|
| 1180 |
+
bias_fail = False
|
| 1181 |
+
if banded and (ulp_ok or tol_ok):
|
| 1182 |
+
w_frac = max(t["frac_above_band"] for t in per_trial)
|
| 1183 |
+
w_side = max(t["bias_one_sided_frac"] for t in per_trial)
|
| 1184 |
+
if w_frac > frac_above_tol and w_side >= 0.9:
|
| 1185 |
+
bias_fail = True
|
| 1186 |
+
criterion += ("; FAILED bias gate: frac_above_band %.3f > %s "
|
| 1187 |
+
"with one-sided fraction %.2f" %
|
| 1188 |
+
(w_frac, frac_above_tol, w_side))
|
| 1189 |
+
passed = bool((ulp_ok or tol_ok) and not bias_fail)
|
| 1190 |
+
hints = [] if passed else _compare_hints(worst)
|
| 1191 |
+
if passed:
|
| 1192 |
+
verdict = "pass: " + (
|
| 1193 |
+
f"max {max_seen} ULP within the order band" if banded
|
| 1194 |
+
else f"max {max_seen} ULP")
|
| 1195 |
+
elif bias_fail:
|
| 1196 |
+
verdict = ("fail: uniform bias, %.0f%% of elements above their "
|
| 1197 |
+
"order band in one direction" %
|
| 1198 |
+
(100 * max(t["frac_above_band"] for t in per_trial)))
|
| 1199 |
+
else:
|
| 1200 |
+
w0 = worst["worst_elements"][0] if worst["worst_elements"] else None
|
| 1201 |
+
verdict = "fail: max %d ULP" % max_seen + (
|
| 1202 |
+
" at index %s (ref %.6g, cand %.6g)" %
|
| 1203 |
+
(w0["index_nd"], w0["ref"], w0["cand"]) if w0 else "")
|
| 1204 |
+
rep = {
|
| 1205 |
+
"pass": passed,
|
| 1206 |
+
"verdict": verdict,
|
| 1207 |
"criterion": criterion,
|
| 1208 |
"worst_trial": worst,
|
| 1209 |
"max_abs": max_abs_seen,
|
|
|
|
| 1211 |
"trials": per_trial,
|
| 1212 |
"ulp_hist_log2": dict(sorted(total_hist.items())),
|
| 1213 |
}
|
| 1214 |
+
if hints:
|
| 1215 |
+
rep["hints"] = hints
|
| 1216 |
+
return rep
|
| 1217 |
|
| 1218 |
|
| 1219 |
_FUZZ_VARIANTS = ("contig", "transposed", "strided_rows", "offset_slice",
|
|
|
|
| 1229 |
return False
|
| 1230 |
|
| 1231 |
|
| 1232 |
+
class _RandnMaker:
|
| 1233 |
+
"""Picklable default maker: `arity` same-shape randn tensors (zeros for empty shapes)."""
|
| 1234 |
+
|
| 1235 |
+
def __init__(self, arity=1):
|
| 1236 |
+
self.arity = arity
|
| 1237 |
+
|
| 1238 |
+
def __call__(self, shape, dtype=torch.float32):
|
| 1239 |
+
n = math.prod(shape)
|
| 1240 |
+
if n:
|
| 1241 |
+
out = tuple(torch.randn(tuple(shape), device="cuda",
|
| 1242 |
+
dtype=dtype)
|
| 1243 |
+
for _ in range(self.arity))
|
| 1244 |
+
else:
|
| 1245 |
+
out = tuple(torch.zeros(tuple(shape), device="cuda",
|
| 1246 |
+
dtype=dtype)
|
| 1247 |
+
for _ in range(self.arity))
|
| 1248 |
+
return out if self.arity > 1 else out[0]
|
| 1249 |
+
|
| 1250 |
+
|
| 1251 |
+
def _default_make(arity=1):
|
| 1252 |
+
"""Default maker instance; a class so isolate=True can transport it."""
|
| 1253 |
+
return _RandnMaker(arity)
|
| 1254 |
+
|
| 1255 |
+
|
| 1256 |
def _fuzz_build(make, shape, dtype):
|
| 1257 |
try:
|
| 1258 |
out = make(tuple(shape), dtype)
|
|
|
|
| 1269 |
if v == "strided_rows":
|
| 1270 |
return x[::2] if x.dim() >= 1 and x.shape[0] >= 2 else x
|
| 1271 |
if v == "offset_slice":
|
| 1272 |
+
# storage-offset-1 view: misaligned for vectorized loads
|
| 1273 |
return x.flatten()[1:] if x.numel() > 1 else x
|
| 1274 |
if v == "overflow_expand":
|
| 1275 |
if x.numel() == 0:
|
|
|
|
| 1283 |
|
| 1284 |
def _fuzz_group(op, ref, make, shape, dtype, variants, ulp_tol, device,
|
| 1285 |
stop_on_context_loss=True):
|
| 1286 |
+
"""All variant cases for one (shape, dtype) group; returns (rows, context_lost)."""
|
|
|
|
| 1287 |
rows = []
|
| 1288 |
bases = _fuzz_build(make, shape, dtype)
|
| 1289 |
lost = False
|
|
|
|
| 1320 |
got = op(*xs)
|
| 1321 |
want = ref(*xs)
|
| 1322 |
if v == "overflow_expand":
|
| 1323 |
+
# bounded comparison: head plus the high-index tail where int32 indexing wraps
|
|
|
|
| 1324 |
w = 1 << 20
|
| 1325 |
gf, wf = got.flatten(), want.flatten()
|
| 1326 |
pairs = ((gf[:w], wf[:w]), (gf[-w:], wf[-w:]))
|
|
|
|
| 1345 |
return rows, lost
|
| 1346 |
|
| 1347 |
|
| 1348 |
+
def _fn_spec(fn, name):
|
| 1349 |
+
"""Picklable transport spec for a callable, with a source fallback for local defs."""
|
| 1350 |
+
import pickle
|
| 1351 |
+
try:
|
| 1352 |
+
return ("pickle", pickle.dumps(fn), None)
|
| 1353 |
+
except Exception: # noqa: BLE001
|
| 1354 |
+
import inspect
|
| 1355 |
+
import textwrap
|
| 1356 |
+
try:
|
| 1357 |
+
src = textwrap.dedent(inspect.getsource(fn))
|
| 1358 |
+
except (OSError, TypeError):
|
| 1359 |
+
src = ""
|
| 1360 |
+
if not src.startswith("def "):
|
| 1361 |
+
raise ValueError(
|
| 1362 |
+
f"{name} is not picklable and not a plain def; define "
|
| 1363 |
+
"op/ref/make as top-level functions (not lambdas or "
|
| 1364 |
+
"closures) to use isolate=True") from None
|
| 1365 |
+
return ("source", src, fn.__name__)
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
_POOL_BOOTSTRAP = r"""
|
| 1369 |
+
import json, os, pickle, sys
|
| 1370 |
meta = json.load(open(sys.argv[1], "r"))
|
| 1371 |
+
sys.path[:0] = meta["sys_path"]
|
| 1372 |
try:
|
| 1373 |
import kernels
|
| 1374 |
_mk = kernels.get_kernel(meta["repo"], version=meta["version"],
|
|
|
|
| 1381 |
import load_local
|
| 1382 |
_mk = load_local.load()
|
| 1383 |
import torch
|
| 1384 |
+
_ns = {"torch": torch}
|
| 1385 |
+
def _resolve(spec):
|
| 1386 |
+
kind, payload, name = spec
|
| 1387 |
+
if kind == "pickle":
|
| 1388 |
+
return pickle.loads(payload)
|
| 1389 |
+
exec(payload, _ns)
|
| 1390 |
+
return _ns[name]
|
| 1391 |
+
_op, _ref, _make = (_resolve(s) for s in
|
| 1392 |
+
pickle.load(open(meta["fns"], "rb")))
|
| 1393 |
+
print("ready", flush=True)
|
| 1394 |
+
for line in sys.stdin:
|
| 1395 |
+
job = json.loads(line)
|
| 1396 |
+
rows, _l = _mk._fuzz_group(
|
| 1397 |
+
_op, _ref, _make, tuple(job["shape"]),
|
| 1398 |
+
getattr(torch, job["dtype"]), tuple(job["variants"]),
|
| 1399 |
+
job["ulp_tol"], job["device"], stop_on_context_loss=False)
|
| 1400 |
+
pickle.dump(rows, open(job["out"], "wb"))
|
| 1401 |
+
print("done", flush=True)
|
| 1402 |
"""
|
| 1403 |
|
| 1404 |
|
| 1405 |
+
class _FuzzPool:
|
| 1406 |
+
"""One persistent isolation worker fed groups over stdin, respawned only after a crash."""
|
| 1407 |
+
|
| 1408 |
+
def __init__(self, meta_path, stderr_path, timeout_s):
|
| 1409 |
+
self.meta_path = meta_path
|
| 1410 |
+
self.stderr_path = stderr_path
|
| 1411 |
+
self.timeout_s = timeout_s
|
| 1412 |
+
self.proc = None
|
| 1413 |
+
self.q = None
|
| 1414 |
+
|
| 1415 |
+
def _spawn(self):
|
| 1416 |
+
import queue
|
| 1417 |
+
import subprocess
|
| 1418 |
+
import sys as _sys
|
| 1419 |
+
import threading
|
| 1420 |
+
self.errf = open(self.stderr_path, "ab")
|
| 1421 |
+
self.proc = subprocess.Popen(
|
| 1422 |
+
[_sys.executable, "-c", _POOL_BOOTSTRAP, self.meta_path],
|
| 1423 |
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
| 1424 |
+
stderr=self.errf, text=True)
|
| 1425 |
+
self.q = queue.Queue()
|
| 1426 |
+
|
| 1427 |
+
def reader(p, q):
|
| 1428 |
+
for line in p.stdout:
|
| 1429 |
+
q.put(line.strip())
|
| 1430 |
+
threading.Thread(target=reader, args=(self.proc, self.q),
|
| 1431 |
+
daemon=True).start()
|
| 1432 |
+
try:
|
| 1433 |
+
first = self.q.get(timeout=self.timeout_s)
|
| 1434 |
+
except Exception: # noqa: BLE001
|
| 1435 |
+
first = None
|
| 1436 |
+
return first == "ready"
|
| 1437 |
+
|
| 1438 |
+
def run(self, job_line):
|
| 1439 |
+
"""Returns True when the worker reports done, False on crash or timeout."""
|
| 1440 |
+
import queue
|
| 1441 |
+
if self.proc is None or self.proc.poll() is not None:
|
| 1442 |
+
if not self._spawn():
|
| 1443 |
+
return False
|
| 1444 |
+
try:
|
| 1445 |
+
self.proc.stdin.write(job_line + "\n")
|
| 1446 |
+
self.proc.stdin.flush()
|
| 1447 |
+
except OSError:
|
| 1448 |
+
return False
|
| 1449 |
+
try:
|
| 1450 |
+
return self.q.get(timeout=self.timeout_s) == "done"
|
| 1451 |
+
except queue.Empty:
|
| 1452 |
+
if self.proc.poll() is None:
|
| 1453 |
+
self.proc.kill()
|
| 1454 |
+
return False
|
| 1455 |
+
|
| 1456 |
+
def stderr_tail(self):
|
| 1457 |
+
try:
|
| 1458 |
+
self.errf.flush()
|
| 1459 |
+
with open(self.stderr_path, "rb") as f:
|
| 1460 |
+
return f.read()[-400:].decode(errors="replace")
|
| 1461 |
+
except OSError:
|
| 1462 |
+
return ""
|
| 1463 |
+
|
| 1464 |
+
def close(self):
|
| 1465 |
+
if self.proc is not None and self.proc.poll() is None:
|
| 1466 |
+
self.proc.kill()
|
| 1467 |
+
try:
|
| 1468 |
+
self.errf.close()
|
| 1469 |
+
except Exception: # noqa: BLE001
|
| 1470 |
+
pass
|
| 1471 |
+
|
| 1472 |
+
|
| 1473 |
+
def _fuzz_hints(rows):
|
| 1474 |
+
hints = []
|
| 1475 |
+
bad = [c for c in rows if c["status"] == "mismatch"]
|
| 1476 |
+
if bad and all(c["variant"] in ("transposed", "strided_rows",
|
| 1477 |
+
"offset_slice") for c in bad):
|
| 1478 |
+
hints.append("mismatches only on non-contiguous variants: the op "
|
| 1479 |
+
"assumes contiguity; call .contiguous() or honor "
|
| 1480 |
+
"strides")
|
| 1481 |
+
if any(c["variant"] == "overflow_expand" and
|
| 1482 |
+
c["status"] == "mismatch" for c in rows):
|
| 1483 |
+
hints.append("overflow_expand mismatch: int32 indexing wraps past "
|
| 1484 |
+
"2^31 elements; use 64-bit index arithmetic")
|
| 1485 |
+
return hints
|
| 1486 |
+
|
| 1487 |
+
|
| 1488 |
+
def fuzz_shapes(op, ref, make=None, shapes=None, variants=_FUZZ_VARIANTS,
|
| 1489 |
dtypes=(torch.float32,), ulp_tol=64, isolate=False,
|
| 1490 |
+
isolate_timeout_s=180, device="cuda", arity=1):
|
| 1491 |
+
"""Edge shapes, dtypes, and layout mutations with per-case repro recipes; isolate=True survives context-killing candidates via a pooled worker."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1492 |
if shapes is None:
|
| 1493 |
shapes = [(), (7,), (0, 8), (1, 1), (1, 17), (3, 63), (17, 129),
|
| 1494 |
(128, 128), (1023, 5), (2, 4096), (2, 3, 5)]
|
| 1495 |
+
if make is None:
|
| 1496 |
+
make = _default_make(arity)
|
| 1497 |
|
| 1498 |
rows = []
|
| 1499 |
lost = False
|
| 1500 |
if isolate:
|
| 1501 |
import json
|
| 1502 |
import pickle
|
|
|
|
| 1503 |
import sys as _sys
|
| 1504 |
import tempfile
|
| 1505 |
tmpdir = tempfile.mkdtemp(prefix="mk_fuzz_")
|
| 1506 |
fns = os.path.join(tmpdir, "fns.pkl")
|
| 1507 |
+
specs = [_fn_spec(f, n) for f, n in ((op, "op"), (ref, "ref"),
|
| 1508 |
+
(make, "make"))]
|
| 1509 |
with open(fns, "wb") as f:
|
| 1510 |
+
pickle.dump(specs, f)
|
| 1511 |
+
meta_path = os.path.join(tmpdir, "meta.json")
|
| 1512 |
+
with open(meta_path, "w", encoding="utf-8") as f:
|
| 1513 |
+
json.dump({"sys_path": [p for p in _sys.path
|
| 1514 |
+
if isinstance(p, str)],
|
| 1515 |
+
"repo": _REPO_ID, "version": _REPO_VERSION,
|
| 1516 |
+
"fns": fns}, f)
|
| 1517 |
+
pool = _FuzzPool(meta_path, os.path.join(tmpdir, "stderr.log"),
|
| 1518 |
+
isolate_timeout_s)
|
| 1519 |
+
try:
|
| 1520 |
+
for dtype in dtypes:
|
| 1521 |
+
for shape in shapes:
|
| 1522 |
+
out = os.path.join(tmpdir, "rows.pkl")
|
| 1523 |
+
if os.path.exists(out):
|
| 1524 |
+
os.remove(out)
|
| 1525 |
+
job = json.dumps({
|
| 1526 |
"shape": list(shape),
|
| 1527 |
"dtype": str(dtype).replace("torch.", ""),
|
| 1528 |
"variants": list(variants),
|
| 1529 |
"ulp_tol": ulp_tol, "device": device,
|
| 1530 |
+
"out": out})
|
| 1531 |
+
ok = pool.run(job)
|
| 1532 |
+
if ok and os.path.exists(out):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
with open(out, "rb") as f:
|
| 1534 |
+
rows.extend(pickle.load(f))
|
| 1535 |
+
else:
|
| 1536 |
+
rows.append({
|
| 1537 |
+
"shape": tuple(shape), "variant": "*",
|
| 1538 |
+
"dtype": str(dtype).replace("torch.", ""),
|
| 1539 |
+
"status": "crashed_isolated",
|
| 1540 |
+
"stderr_tail": pool.stderr_tail()})
|
| 1541 |
+
finally:
|
| 1542 |
+
pool.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1543 |
else:
|
| 1544 |
pending = [(dt, sh) for dt in dtypes for sh in shapes]
|
| 1545 |
for gi, (dtype, shape) in enumerate(pending):
|
|
|
|
| 1556 |
n_bad = sum(1 for r in rows
|
| 1557 |
if r["status"] not in ("ok", "skipped_vram",
|
| 1558 |
"skipped_empty"))
|
| 1559 |
+
rep = {"cases": rows, "failed": n_bad, "total": len(rows),
|
| 1560 |
+
"context_lost": lost}
|
| 1561 |
+
hints = _fuzz_hints(rows)
|
| 1562 |
+
if hints:
|
| 1563 |
+
rep["hints"] = hints
|
| 1564 |
+
if n_bad == 0:
|
| 1565 |
+
rep["verdict"] = "pass: %d/%d cases ok" % (len(rows) - n_bad,
|
| 1566 |
+
len(rows))
|
| 1567 |
+
else:
|
| 1568 |
+
worst_case = next(c for c in rows
|
| 1569 |
+
if c["status"] not in ("ok", "skipped_vram",
|
| 1570 |
+
"skipped_empty"))
|
| 1571 |
+
rep["verdict"] = "fail: %d/%d cases (%s at %s %s)" % (
|
| 1572 |
+
n_bad, len(rows), worst_case["status"], worst_case["shape"],
|
| 1573 |
+
worst_case["variant"])
|
| 1574 |
+
return rep
|
| 1575 |
|
| 1576 |
|
| 1577 |
def sweep(factory, grid, iters=30, warmup=5, bytes=None, flops=None,
|
| 1578 |
check=None, budget_s=None, race=False):
|
| 1579 |
+
"""Launch-configuration search, fastest first, with a correctness gate per row and no silently dropped points."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1580 |
keys = list(grid.keys())
|
| 1581 |
|
| 1582 |
def expand(i, acc):
|
|
|
|
| 1641 |
continue
|
| 1642 |
rows.append(run_point(p, iters))
|
| 1643 |
rows.sort(key=lambda r: (not r.get("check", True), r["median_ms"]))
|
| 1644 |
+
if rows:
|
| 1645 |
+
n_pruned = sum(1 for r in rows if r.get("status") == "pruned_race")
|
| 1646 |
+
n_skip = sum(1 for r in rows if r.get("status") == "not_run")
|
| 1647 |
+
best = rows[0]
|
| 1648 |
+
rows_verdict = "fastest %s at %.4g ms" % (
|
| 1649 |
+
{k: best[k] for k in keys}, best["median_ms"])
|
| 1650 |
+
if n_pruned:
|
| 1651 |
+
rows_verdict += "; %d pruned" % n_pruned
|
| 1652 |
+
if n_skip:
|
| 1653 |
+
rows_verdict += "; %d not run" % n_skip
|
| 1654 |
+
best["verdict"] = rows_verdict
|
| 1655 |
return rows
|
| 1656 |
|
| 1657 |
|
| 1658 |
+
def judge(cand, ref, inputs=None, gen=None, bytes=None, flops=None,
|
| 1659 |
+
dtype=None, order_shuffle=None, make=None, quick_fuzz=True,
|
| 1660 |
+
device="cuda"):
|
| 1661 |
+
"""One-call verdict: compare, a quick fuzz, and a roofline bench against the cached dossier."""
|
| 1662 |
+
if (gen is None) == (inputs is None):
|
| 1663 |
+
raise ValueError("pass exactly one of inputs (concrete tensors) "
|
| 1664 |
+
"or gen (seed -> args)")
|
| 1665 |
+
if inputs is not None and isinstance(inputs, torch.Tensor):
|
| 1666 |
+
inputs = (inputs,)
|
| 1667 |
+
args0 = tuple(inputs) if inputs is not None else gen(0)
|
| 1668 |
+
arity = len(args0)
|
| 1669 |
+
if dtype is None:
|
| 1670 |
+
dt0 = args0[0].dtype
|
| 1671 |
+
dtype = {torch.float16: "fp16", torch.bfloat16: "bf16"}.get(
|
| 1672 |
+
dt0, "f32")
|
| 1673 |
+
dossier = probe_device(device, quick=True, cached=True,
|
| 1674 |
+
tenancy_check=False)
|
| 1675 |
+
cmp_rep = compare(ref, cand, gen=gen, inputs=inputs, trials=4,
|
| 1676 |
+
order_shuffle=order_shuffle)
|
| 1677 |
+
fuzz_rep = None
|
| 1678 |
+
if quick_fuzz:
|
| 1679 |
+
fuzz_rep = fuzz_shapes(
|
| 1680 |
+
cand, ref, make=make, arity=arity,
|
| 1681 |
+
shapes=[(1, 17), (3, 63), (128, 128)],
|
| 1682 |
+
variants=("contig", "transposed", "offset_slice"),
|
| 1683 |
+
dtypes=(args0[0].dtype,))
|
| 1684 |
+
bench_rep = None
|
| 1685 |
+
if inputs is not None:
|
| 1686 |
+
bench_rep = bench(lambda: cand(*inputs), iters=32, warmup=6,
|
| 1687 |
+
bytes=bytes, flops=flops, dossier=dossier,
|
| 1688 |
+
dtype=dtype, device=device)
|
| 1689 |
+
passed = cmp_rep["pass"] and (fuzz_rep is None or
|
| 1690 |
+
fuzz_rep["failed"] == 0)
|
| 1691 |
+
parts = [cmp_rep["verdict"]]
|
| 1692 |
+
if fuzz_rep is not None:
|
| 1693 |
+
parts.append(fuzz_rep["verdict"])
|
| 1694 |
+
if bench_rep is not None:
|
| 1695 |
+
parts.append(bench_rep["verdict"])
|
| 1696 |
+
rep = {"pass": passed, "verdict" : " | ".join(parts),
|
| 1697 |
+
"compare": cmp_rep, "fuzz": fuzz_rep, "bench": bench_rep,
|
| 1698 |
+
"device": dossier["device"]["name"]}
|
| 1699 |
+
hints = list(cmp_rep.get("hints", []))
|
| 1700 |
+
if fuzz_rep is not None:
|
| 1701 |
+
hints += fuzz_rep.get("hints", [])
|
| 1702 |
+
if hints:
|
| 1703 |
+
rep["hints"] = hints
|
| 1704 |
+
return rep
|
| 1705 |
+
|
| 1706 |
+
|
| 1707 |
def alloc_stamps(n, blocks=None, device="cuda"):
|
| 1708 |
+
"""Device buffer for MK_STAMP (1-D) or MK_STAMP_BLOCK ([blocks, n]) stamps."""
|
|
|
|
|
|
|
| 1709 |
if blocks is not None:
|
| 1710 |
return torch.zeros(blocks, n, dtype=torch.int64, device=device)
|
| 1711 |
return torch.zeros(n, dtype=torch.int64, device=device)
|
|
|
|
| 1715 |
|
| 1716 |
|
| 1717 |
def pinned_dossiers():
|
| 1718 |
+
"""Names of the release-pinned dossiers, one per certified architecture."""
|
|
|
|
| 1719 |
return sorted(_PINNED.keys())
|
| 1720 |
|
| 1721 |
|
|
|
|
| 1734 |
|
| 1735 |
|
| 1736 |
def dossier_drift(current, pinned=None, rel_tol=0.35, skip=_DRIFT_SKIP):
|
| 1737 |
+
"""Per-leaf relative drift of a fresh dossier against the release pin for the same architecture."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1738 |
if pinned is None:
|
| 1739 |
arch = current["device"]["sm_arch"]
|
| 1740 |
matches = [k for k, v in _PINNED.items()
|
|
|
|
| 1757 |
"rel": round(rel, 4)})
|
| 1758 |
worst = max((e["rel"] for e in entries), default=0.0)
|
| 1759 |
over = [e for e in entries if e["rel"] > rel_tol]
|
| 1760 |
+
rep = {"entries": entries, "worst_rel": worst, "over_tol": over,
|
| 1761 |
+
"rel_tol": rel_tol, "compared": len(entries),
|
| 1762 |
+
"pass": len(over) == 0}
|
| 1763 |
+
rep["verdict"] = ("pass: worst drift %.3f over %d entries" %
|
| 1764 |
+
(worst, len(entries))) if rep["pass"] else \
|
| 1765 |
+
("fail: %d entries over %.2f, worst %s at %.3f" %
|
| 1766 |
+
(len(over), rel_tol, over[0]["path"], worst))
|
| 1767 |
+
return rep
|
| 1768 |
|
| 1769 |
|
| 1770 |
def stamped_demo(n=1 << 22, fma_iters=4000, blocks=None, threads=256,
|
| 1771 |
device="cuda"):
|
| 1772 |
+
"""Run the shipped MK_STAMP_BLOCK example (load, compute, drained store) and decode its stamps."""
|
|
|
|
|
|
|
| 1773 |
props = torch.cuda.get_device_properties(device)
|
| 1774 |
if blocks is None:
|
| 1775 |
blocks = props.multi_processor_count
|
|
|
|
| 1782 |
|
| 1783 |
|
| 1784 |
def read_stamps(stamps, labels=None, clock_ghz=None):
|
| 1785 |
+
"""Decode MK_STAMP deltas to ticks and microseconds; 2-D buffers yield min/median/max across blocks."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1786 |
if clock_ghz is None:
|
| 1787 |
clock_ghz = measure_clock(stamps.device)
|
| 1788 |
|
|
|
|
| 1818 |
return {"phases": phases, "clock_ghz": round(clock_ghz, 3),
|
| 1819 |
"blocks_reporting": int(live.sum().item()),
|
| 1820 |
"blocks_total": int(live.numel())}
|
| 1821 |
+
|
| 1822 |
+
|
| 1823 |
+
_SCHEMAS = {
|
| 1824 |
+
"probe_device": {
|
| 1825 |
+
"device": "name, sm_arch, sms, l2_bytes, vram_bytes, torch, "
|
| 1826 |
+
"cuda_runtime, ecc, driver, power_limit_w",
|
| 1827 |
+
"clock": "max/min/final GHz under sustained FMA load plus sag_pct",
|
| 1828 |
+
"dram_bw": "GB/s: triad_4B/8B/16B, read_only, write_only, "
|
| 1829 |
+
"random_gather_16B",
|
| 1830 |
+
"l2_bw": "GB/s over an L2-resident working set",
|
| 1831 |
+
"latency": "cycles: smem, l1, l2, dram (min of 3 chases) plus "
|
| 1832 |
+
"ns_per_cycle",
|
| 1833 |
+
"fma_f32 / fma_f64": "TFLOP/s of the register-resident pipes",
|
| 1834 |
+
"fma_ilp": "TFLOP/s at 1/2/4 chains plus ratio_4_1; near 4 means "
|
| 1835 |
+
"latency-bound",
|
| 1836 |
+
"mma": "TFLOP/s per dtype; sparse carries executed and "
|
| 1837 |
+
"dense_equivalent; verified_k_lanes carries "
|
| 1838 |
+
"executed_k_per_iter, vs_nominal, calibration_linear; "
|
| 1839 |
+
"fp8_accum carries the accumulate-precision stall",
|
| 1840 |
+
"mma_fed": "TFLOP/s of smem-staged fp16 tiles (staging exposed)",
|
| 1841 |
+
"smem_bw": "GB/s aggregate, conflict-light pattern",
|
| 1842 |
+
"smem_conflict_penalty": "time ratio vs stride 1 per lane stride",
|
| 1843 |
+
"transfer_bw": "GB/s: h2d/d2h pinned and pageable, d2d",
|
| 1844 |
+
"atomics_f32": "Gop/s curve over distinct addresses plus "
|
| 1845 |
+
"shared_same_addr",
|
| 1846 |
+
"launch_overhead": "us: torch_queued, torch_round_trip, "
|
| 1847 |
+
"raw_queued, raw_round_trip",
|
| 1848 |
+
"grid_barrier": "us per grid.sync or 'unsupported'",
|
| 1849 |
+
"occupancy_blocks_per_sm": "API table by (threads, dyn smem)",
|
| 1850 |
+
"occupancy_knees_fma_tflops": "timed dependent-chain table",
|
| 1851 |
+
},
|
| 1852 |
+
"bench": {
|
| 1853 |
+
"median_ms": "median over accepted samples",
|
| 1854 |
+
"sustained_ms": "median over all samples",
|
| 1855 |
+
"median_ci95_ms / p05_ms / p95_ms / min_ms / mean_ms":
|
| 1856 |
+
"distribution of accepted samples",
|
| 1857 |
+
"samples / rejected_throttled / rejection_basis":
|
| 1858 |
+
"counts and whether rejection used sm_clock or "
|
| 1859 |
+
"bandwidth_consistency",
|
| 1860 |
+
"no_clean_window": "present when every window ran throttled; the "
|
| 1861 |
+
"median is then a throttled-regime number",
|
| 1862 |
+
"clock_ghz": "best and worst SM clock probes around the run",
|
| 1863 |
+
"achieved_gbs / achieved_tflops / arithmetic_intensity":
|
| 1864 |
+
"rates from declared traffic",
|
| 1865 |
+
"roofline": "bound, ridge_flops_per_byte, pct_of_roof, peak_dtype",
|
| 1866 |
+
"graph_captured / graph_fallback_reason": "CUDA-graph state",
|
| 1867 |
+
"alloc_growth_bytes_per_call": "present when fn leaks allocations",
|
| 1868 |
+
"host_overhead_suspected": "present when fn syncs or does host "
|
| 1869 |
+
"work inside; ops under ~50 us are "
|
| 1870 |
+
"below the detection floor",
|
| 1871 |
+
"verdict": "one-sentence summary",
|
| 1872 |
+
},
|
| 1873 |
+
"compare": {
|
| 1874 |
+
"pass": "final verdict incl. the one-sided bias gate",
|
| 1875 |
+
"verdict / criterion / hints": "summary, acceptance rule, and "
|
| 1876 |
+
"failure-pattern suggestions",
|
| 1877 |
+
"worst_trial": "max_ulp, mean_ulp, first_divergent_index(+_nd), "
|
| 1878 |
+
"NaN/Inf census, order_band_ulp, band_score, "
|
| 1879 |
+
"frac_above_band, bias_one_sided_frac, "
|
| 1880 |
+
"worst_elements (index, index_nd, ref, cand, ulp)",
|
| 1881 |
+
"max_abs / max_rel / ulp_hist_log2": "aggregate evidence",
|
| 1882 |
+
"floor": "the banded route cannot reject errors under "
|
| 1883 |
+
"band_margin*(band+2) ULP at order-stable elements",
|
| 1884 |
+
},
|
| 1885 |
+
"fuzz_shapes": {
|
| 1886 |
+
"cases": "per-case shape, variant, dtype, status, repro recipe",
|
| 1887 |
+
"status values": "ok | mismatch | exception | context_killed | "
|
| 1888 |
+
"context_lost | crashed_isolated | skipped_vram "
|
| 1889 |
+
"| skipped_empty",
|
| 1890 |
+
"failed / total / context_lost / verdict / hints": "aggregates",
|
| 1891 |
+
},
|
| 1892 |
+
"sweep": {
|
| 1893 |
+
"rows": "grid point plus median_ms, rejected_throttled, check?, "
|
| 1894 |
+
"achieved rates, status in pruned_race | not_run",
|
| 1895 |
+
"verdict": "on the fastest row",
|
| 1896 |
+
},
|
| 1897 |
+
"dossier_drift": {
|
| 1898 |
+
"entries": "path, current, pinned, rel per intersecting leaf",
|
| 1899 |
+
"worst_rel / over_tol / compared / pass / verdict": "aggregates",
|
| 1900 |
+
},
|
| 1901 |
+
"judge": {
|
| 1902 |
+
"pass / verdict / hints": "composite over compare, fuzz, bench",
|
| 1903 |
+
"compare / fuzz / bench": "the full sub-reports",
|
| 1904 |
+
},
|
| 1905 |
+
"read_stamps": {
|
| 1906 |
+
"phases": "ticks and us per phase; 2-D input adds min/median/max "
|
| 1907 |
+
"across blocks",
|
| 1908 |
+
"note": "stamps record issue; DRAIN variants include the store "
|
| 1909 |
+
"drain to the coherence point",
|
| 1910 |
+
},
|
| 1911 |
+
}
|
| 1912 |
+
|
| 1913 |
+
|
| 1914 |
+
def schema(name=None):
|
| 1915 |
+
"""Field layout of every report, so an agent can look up what a number means."""
|
| 1916 |
+
if name is None:
|
| 1917 |
+
return _SCHEMAS
|
| 1918 |
+
if name not in _SCHEMAS:
|
| 1919 |
+
raise KeyError(f"no schema for {name!r}; have "
|
| 1920 |
+
f"{sorted(_SCHEMAS)}")
|
| 1921 |
+
return _SCHEMAS[name]
|
build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_0b9de42.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8161b3fe4adbec8d22ecff765130ad6b9893ecca46b1e65c09398d763750b592
|
| 3 |
+
size 1434632
|
build/torch213-cxx11-cu132-x86_64-linux/_metakernel_cuda_b62a1ce.abi3.so
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:6ce8f061538f15eb45b47250112237c577a079f3ed6b7c54a5ff6d963ff0ba65
|
| 3 |
-
size 1188488
|
|
|
|
|
|
|
|
|
|
|
|
build/torch213-cxx11-cu132-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _metakernel_cuda_0b9de42
|
| 3 |
+
ops = torch.ops._metakernel_cuda_0b9de42
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_metakernel_cuda_0b9de42::{op_name}"
|
build/torch213-cxx11-cu132-x86_64-linux/_pinned.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
Generated by dev/pin_dossier.py from full probe_device() runs on idle,
|
| 4 |
-
single-tenant cards; consumed by metakernel.dossier_drift.
|
| 5 |
-
"""
|
| 6 |
|
| 7 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 8 |
'confidence': 'high',
|
|
@@ -295,7 +291,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 295 |
'd2h_pinned': 10.8,
|
| 296 |
'h2d_pageable': 10.3,
|
| 297 |
'h2d_pinned': 11.8}}},
|
| 298 |
-
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.
|
| 299 |
'confidence': 'high',
|
| 300 |
'method': 'atomicAdd '
|
| 301 |
'hammer; curve '
|
|
@@ -308,21 +304,21 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 308 |
'contention) to '
|
| 309 |
'one per thread',
|
| 310 |
'units': 'Gop/s',
|
| 311 |
-
'value': {'curve_slots': {'1': 0.
|
| 312 |
-
'145408':
|
| 313 |
-
'4096':
|
| 314 |
-
'512':
|
| 315 |
-
'64': 13.
|
| 316 |
-
'8': 5.
|
| 317 |
-
'shared_same_addr': 12.
|
| 318 |
'clock': {'confidence': 'high',
|
| 319 |
-
'final_ghz': 2.
|
| 320 |
-
'max_ghz': 2.
|
| 321 |
'method': '24 back-to-back FMA '
|
| 322 |
'spin windows of 30M '
|
| 323 |
'ticks; wall/ticks '
|
| 324 |
'per window',
|
| 325 |
-
'min_ghz': 2.
|
| 326 |
'sag_pct': 0.3,
|
| 327 |
'units': 'GHz'},
|
| 328 |
'device': {'cuda_runtime': '12.6',
|
|
@@ -336,7 +332,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 336 |
'sms': 142,
|
| 337 |
'torch': '2.10.0+cu126',
|
| 338 |
'vram_bytes': 48305274880},
|
| 339 |
-
'dram_bw': {'clock_ghz': 2.
|
| 340 |
'confidence': 'high',
|
| 341 |
'method': 'streaming triad '
|
| 342 |
'c=a+s*b per access '
|
|
@@ -356,26 +352,41 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 356 |
'line-efficiency '
|
| 357 |
'claim',
|
| 358 |
'units': 'GB/s',
|
| 359 |
-
'value': {'random_gather_16B': 218.
|
| 360 |
-
'read_only':
|
| 361 |
-
'triad_16B':
|
| 362 |
-
'triad_4B':
|
| 363 |
-
'triad_8B':
|
| 364 |
'write_only': 766.5}},
|
| 365 |
-
'fma_f32': {'clock_ghz': 2.
|
| 366 |
'confidence': 'high',
|
| 367 |
'method': 'register-resident '
|
| 368 |
'FMA chains',
|
| 369 |
'units': 'TFLOP/s',
|
| 370 |
-
'value':
|
| 371 |
-
'fma_f64': {'clock_ghz': 2.
|
| 372 |
'confidence': 'high',
|
| 373 |
'method': 'register-resident '
|
| 374 |
'double FMA chains '
|
| 375 |
'(the DFMA pipe)',
|
| 376 |
'units': 'TFLOP/s',
|
| 377 |
-
'value': 1.
|
| 378 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
'confidence': 'high',
|
| 380 |
'method': 'cooperative '
|
| 381 |
'kernel, 142 '
|
|
@@ -383,8 +394,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 383 |
'threads',
|
| 384 |
'units': 'us per '
|
| 385 |
'grid.sync',
|
| 386 |
-
'value': 0.
|
| 387 |
-
'l2_bw': {'clock_ghz': 2.
|
| 388 |
'confidence': 'medium',
|
| 389 |
'method': 'in-kernel repeated '
|
| 390 |
'triad passes over 72 '
|
|
@@ -394,14 +405,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 394 |
'from HBM; amortized '
|
| 395 |
'over 64 passes',
|
| 396 |
'units': 'GB/s',
|
| 397 |
-
'value':
|
| 398 |
-
'latency': {'clock_ghz': 2.
|
| 399 |
'confidence': 'high',
|
| 400 |
'method': 'Sattolo-cycle '
|
| 401 |
-
'pointer chase
|
| 402 |
-
'
|
| 403 |
-
'
|
| 404 |
-
'ring
|
|
|
|
| 405 |
'self-evicting '
|
| 406 |
'chain (2x L2 in '
|
| 407 |
'touched lines) for '
|
|
@@ -413,40 +425,58 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 413 |
'footprints',
|
| 414 |
'units': 'cycles (single-lane '
|
| 415 |
'dependent loads)',
|
| 416 |
-
'value': {'dram_cycles':
|
| 417 |
'l1_cycles': 45.8,
|
| 418 |
-
'l2_cycles':
|
| 419 |
'ns_per_cycle': 0.372,
|
| 420 |
'smem_cycles': 30.0}},
|
| 421 |
-
'launch_overhead': {'clock_ghz': 2.
|
| 422 |
-
'confidence': '
|
| 423 |
'method': 'empty-kernel '
|
| 424 |
-
'trains
|
| 425 |
-
'
|
| 426 |
-
'launch
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
'units': 'us',
|
| 432 |
-
'value': {'
|
| 433 |
-
'
|
| 434 |
-
|
|
|
|
|
|
|
| 435 |
'confidence': 'medium',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
'method': 'register-fragment '
|
| 437 |
'mma_sync loops (wmma; '
|
| 438 |
'fp8 and 2:4-sparse '
|
| 439 |
'fp16 via inline PTX '
|
| 440 |
-
'm16n8k32;
|
| 441 |
-
'
|
| 442 |
-
'
|
| 443 |
-
'
|
| 444 |
-
'
|
| 445 |
-
'
|
| 446 |
-
'
|
| 447 |
-
'
|
| 448 |
-
'
|
| 449 |
-
'
|
| 450 |
'note': 'dense-tile rate without '
|
| 451 |
'memory traffic; real '
|
| 452 |
'kernels see this only '
|
|
@@ -454,25 +484,54 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 454 |
'behind it',
|
| 455 |
'units': 'TFLOP/s (TOP/s for '
|
| 456 |
'int8)',
|
| 457 |
-
'value': {'bf16':
|
| 458 |
-
'fp16':
|
| 459 |
-
'fp16_sparse_2_4':
|
| 460 |
-
|
| 461 |
-
'
|
| 462 |
-
'
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
| 464 |
'vs_nominal': 1.0},
|
| 465 |
-
'fp16': {'
|
|
|
|
| 466 |
'vs_nominal': 1.0},
|
| 467 |
-
'fp16_sparse_2_4': {'
|
|
|
|
| 468 |
'vs_nominal': 1.0},
|
| 469 |
-
'fp8_e4m3': {'
|
| 470 |
-
'
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
'vs_nominal': 1.0},
|
| 473 |
-
'tf32': {'
|
|
|
|
| 474 |
'vs_nominal': 1.0}}},
|
| 475 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
'confidence': 'high',
|
| 477 |
'method': 'occupancy '
|
| 478 |
'API '
|
|
@@ -501,7 +560,7 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 501 |
'16KB': 3,
|
| 502 |
'32KB': 3,
|
| 503 |
'48KB': 2}}},
|
| 504 |
-
'occupancy_knees_fma_tflops': {'clock_ghz': 2.
|
| 505 |
'confidence': 'high',
|
| 506 |
'method': 'a '
|
| 507 |
'single '
|
|
@@ -521,17 +580,6 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 521 |
'fixed '
|
| 522 |
'total '
|
| 523 |
'work; '
|
| 524 |
-
'throughput '
|
| 525 |
-
'is '
|
| 526 |
-
'proportional '
|
| 527 |
-
'to '
|
| 528 |
-
'resident '
|
| 529 |
-
'threads '
|
| 530 |
-
'until '
|
| 531 |
-
'the '
|
| 532 |
-
'pipe '
|
| 533 |
-
'saturates, '
|
| 534 |
-
'so '
|
| 535 |
'the '
|
| 536 |
'cliff '
|
| 537 |
'marks '
|
|
@@ -549,15 +597,15 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 549 |
'above',
|
| 550 |
'units': 'TFLOP/s',
|
| 551 |
'value': {'t1024': {'0KB': 75.5,
|
| 552 |
-
'32KB': 73.
|
| 553 |
-
'48KB':
|
| 554 |
-
't128': {'0KB': 79.
|
| 555 |
'32KB': 60.31,
|
| 556 |
-
'48KB':
|
| 557 |
-
't256': {'0KB':
|
| 558 |
-
'32KB':
|
| 559 |
-
'48KB':
|
| 560 |
-
'smem_bw': {'clock_ghz': 2.
|
| 561 |
'confidence': 'medium',
|
| 562 |
'method': 'block-resident '
|
| 563 |
'read+write '
|
|
@@ -566,12 +614,56 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 566 |
'aggregated across '
|
| 567 |
'SMs',
|
| 568 |
'note': 'bank-conflict-light '
|
| 569 |
-
'access pattern;
|
| 570 |
-
'
|
| 571 |
-
'
|
|
|
|
| 572 |
'units': 'GB/s',
|
| 573 |
-
'value':
|
| 574 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
'confidence': 'high',
|
| 576 |
'method': '256 MB torch '
|
| 577 |
'copies, '
|
|
@@ -583,8 +675,8 @@ PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
|
| 583 |
'direction of '
|
| 584 |
'traffic',
|
| 585 |
'units': 'GB/s',
|
| 586 |
-
'value': {'d2d':
|
| 587 |
-
'd2h_pageable':
|
| 588 |
-
'd2h_pinned': 26.
|
| 589 |
-
'h2d_pageable':
|
| 590 |
'h2d_pinned': 24.0}}}}
|
|
|
|
| 1 |
+
'''Release-pinned device dossiers, one per certified architecture; generated by dev/pin_dossier.py from idle single-tenant runs, consumed by dossier_drift.'''
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
PINNED = {'sm86_nvidiageforcertx3070tila': {'atomics_f32': {'clock_ghz': 1.785,
|
| 4 |
'confidence': 'high',
|
|
|
|
| 291 |
'd2h_pinned': 10.8,
|
| 292 |
'h2d_pageable': 10.3,
|
| 293 |
'h2d_pinned': 11.8}}},
|
| 294 |
+
'sm89_nvidiartx6000adagenerati': {'atomics_f32': {'clock_ghz': 2.705,
|
| 295 |
'confidence': 'high',
|
| 296 |
'method': 'atomicAdd '
|
| 297 |
'hammer; curve '
|
|
|
|
| 304 |
'contention) to '
|
| 305 |
'one per thread',
|
| 306 |
'units': 'Gop/s',
|
| 307 |
+
'value': {'curve_slots': {'1': 0.71,
|
| 308 |
+
'145408': 413.99,
|
| 309 |
+
'4096': 184.9,
|
| 310 |
+
'512': 49.84,
|
| 311 |
+
'64': 13.25,
|
| 312 |
+
'8': 5.74},
|
| 313 |
+
'shared_same_addr': 12.23}},
|
| 314 |
'clock': {'confidence': 'high',
|
| 315 |
+
'final_ghz': 2.698,
|
| 316 |
+
'max_ghz': 2.705,
|
| 317 |
'method': '24 back-to-back FMA '
|
| 318 |
'spin windows of 30M '
|
| 319 |
'ticks; wall/ticks '
|
| 320 |
'per window',
|
| 321 |
+
'min_ghz': 2.5,
|
| 322 |
'sag_pct': 0.3,
|
| 323 |
'units': 'GHz'},
|
| 324 |
'device': {'cuda_runtime': '12.6',
|
|
|
|
| 332 |
'sms': 142,
|
| 333 |
'torch': '2.10.0+cu126',
|
| 334 |
'vram_bytes': 48305274880},
|
| 335 |
+
'dram_bw': {'clock_ghz': 2.705,
|
| 336 |
'confidence': 'high',
|
| 337 |
'method': 'streaming triad '
|
| 338 |
'c=a+s*b per access '
|
|
|
|
| 352 |
'line-efficiency '
|
| 353 |
'claim',
|
| 354 |
'units': 'GB/s',
|
| 355 |
+
'value': {'random_gather_16B': 218.1,
|
| 356 |
+
'read_only': 766.5,
|
| 357 |
+
'triad_16B': 682.1,
|
| 358 |
+
'triad_4B': 654.8,
|
| 359 |
+
'triad_8B': 670.9,
|
| 360 |
'write_only': 766.5}},
|
| 361 |
+
'fma_f32': {'clock_ghz': 2.705,
|
| 362 |
'confidence': 'high',
|
| 363 |
'method': 'register-resident '
|
| 364 |
'FMA chains',
|
| 365 |
'units': 'TFLOP/s',
|
| 366 |
+
'value': 86.58},
|
| 367 |
+
'fma_f64': {'clock_ghz': 2.705,
|
| 368 |
'confidence': 'high',
|
| 369 |
'method': 'register-resident '
|
| 370 |
'double FMA chains '
|
| 371 |
'(the DFMA pipe)',
|
| 372 |
'units': 'TFLOP/s',
|
| 373 |
+
'value': 1.257},
|
| 374 |
+
'fma_ilp': {'clock_ghz': 2.705,
|
| 375 |
+
'confidence': 'high',
|
| 376 |
+
'method': '1/2/4 independent '
|
| 377 |
+
'dependent FMA '
|
| 378 |
+
'chains per thread '
|
| 379 |
+
'at one block of 64 '
|
| 380 |
+
'threads per SM; a '
|
| 381 |
+
'ratio near 4 means '
|
| 382 |
+
'latency-bound at '
|
| 383 |
+
'that occupancy',
|
| 384 |
+
'units': 'TFLOP/s',
|
| 385 |
+
'value': {'ilp1': 11.75,
|
| 386 |
+
'ilp2': 23.37,
|
| 387 |
+
'ilp4': 43.03,
|
| 388 |
+
'ratio_4_1': 3.66}},
|
| 389 |
+
'grid_barrier': {'clock_ghz': 2.705,
|
| 390 |
'confidence': 'high',
|
| 391 |
'method': 'cooperative '
|
| 392 |
'kernel, 142 '
|
|
|
|
| 394 |
'threads',
|
| 395 |
'units': 'us per '
|
| 396 |
'grid.sync',
|
| 397 |
+
'value': 0.55},
|
| 398 |
+
'l2_bw': {'clock_ghz': 2.705,
|
| 399 |
'confidence': 'medium',
|
| 400 |
'method': 'in-kernel repeated '
|
| 401 |
'triad passes over 72 '
|
|
|
|
| 405 |
'from HBM; amortized '
|
| 406 |
'over 64 passes',
|
| 407 |
'units': 'GB/s',
|
| 408 |
+
'value': 9590.6},
|
| 409 |
+
'latency': {'clock_ghz': 2.691,
|
| 410 |
'confidence': 'high',
|
| 411 |
'method': 'Sattolo-cycle '
|
| 412 |
+
'pointer chase, min '
|
| 413 |
+
'of 3 runs; 16 KB '
|
| 414 |
+
'ring for L1, 2 MB '
|
| 415 |
+
'TLB-resident ring '
|
| 416 |
+
'for L2, '
|
| 417 |
'self-evicting '
|
| 418 |
'chain (2x L2 in '
|
| 419 |
'touched lines) for '
|
|
|
|
| 425 |
'footprints',
|
| 426 |
'units': 'cycles (single-lane '
|
| 427 |
'dependent loads)',
|
| 428 |
+
'value': {'dram_cycles': 656.9,
|
| 429 |
'l1_cycles': 45.8,
|
| 430 |
+
'l2_cycles': 292.0,
|
| 431 |
'ns_per_cycle': 0.372,
|
| 432 |
'smem_cycles': 30.0}},
|
| 433 |
+
'launch_overhead': {'clock_ghz': 2.705,
|
| 434 |
+
'confidence': 'medium',
|
| 435 |
'method': 'empty-kernel '
|
| 436 |
+
'trains; '
|
| 437 |
+
'torch path '
|
| 438 |
+
'per-launch '
|
| 439 |
+
'through op '
|
| 440 |
+
'dispatch, '
|
| 441 |
+
'raw path '
|
| 442 |
+
'looped '
|
| 443 |
+
'inside one '
|
| 444 |
+
'binding '
|
| 445 |
+
'call',
|
| 446 |
+
'note': 'host-state-dependent; '
|
| 447 |
+
'the '
|
| 448 |
+
'torch-minus-raw '
|
| 449 |
+
'gap is the '
|
| 450 |
+
'dispatch '
|
| 451 |
+
'cost a '
|
| 452 |
+
'torch-launched '
|
| 453 |
+
'candidate '
|
| 454 |
+
'pays',
|
| 455 |
'units': 'us',
|
| 456 |
+
'value': {'raw_queued_us': 6.75,
|
| 457 |
+
'raw_round_trip_us': 13.87,
|
| 458 |
+
'torch_queued_us': 7.09,
|
| 459 |
+
'torch_round_trip_us': 18.53}},
|
| 460 |
+
'mma': {'clock_ghz': 2.705,
|
| 461 |
'confidence': 'medium',
|
| 462 |
+
'fp8_accum': {'fp8_e4m3': {'effective_accum_mantissa_bits': 8,
|
| 463 |
+
'stall_element_value': 16384.0},
|
| 464 |
+
'fp8_e5m2': {'effective_accum_mantissa_bits': 8,
|
| 465 |
+
'stall_element_value': 16384.0}},
|
| 466 |
'method': 'register-fragment '
|
| 467 |
'mma_sync loops (wmma; '
|
| 468 |
'fp8 and 2:4-sparse '
|
| 469 |
'fp16 via inline PTX '
|
| 470 |
+
'm16n8k32); flops '
|
| 471 |
+
'derive from a '
|
| 472 |
+
'sub-saturation '
|
| 473 |
+
'accumulator read-back '
|
| 474 |
+
'with every fragment '
|
| 475 |
+
'element observed, '
|
| 476 |
+
'never the declared '
|
| 477 |
+
'loop shape; sparse '
|
| 478 |
+
'reports executed and '
|
| 479 |
+
'dense-equivalent flops',
|
| 480 |
'note': 'dense-tile rate without '
|
| 481 |
'memory traffic; real '
|
| 482 |
'kernels see this only '
|
|
|
|
| 484 |
'behind it',
|
| 485 |
'units': 'TFLOP/s (TOP/s for '
|
| 486 |
'int8)',
|
| 487 |
+
'value': {'bf16': 369.6,
|
| 488 |
+
'fp16': 365.3,
|
| 489 |
+
'fp16_sparse_2_4': {'dense_equivalent': 711.1,
|
| 490 |
+
'executed': 355.6},
|
| 491 |
+
'fp8_e4m3': 772.3,
|
| 492 |
+
'fp8_e5m2': 771.2,
|
| 493 |
+
'int8': 750.0,
|
| 494 |
+
'tf32': 178.3},
|
| 495 |
+
'verified_k_lanes': {'bf16': {'calibration_linear': True,
|
| 496 |
+
'executed_k_per_iter': 64.0,
|
| 497 |
'vs_nominal': 1.0},
|
| 498 |
+
'fp16': {'calibration_linear': True,
|
| 499 |
+
'executed_k_per_iter': 64.0,
|
| 500 |
'vs_nominal': 1.0},
|
| 501 |
+
'fp16_sparse_2_4': {'calibration_linear': True,
|
| 502 |
+
'executed_k_per_iter': 128.0,
|
| 503 |
'vs_nominal': 1.0},
|
| 504 |
+
'fp8_e4m3': {'calibration_linear': True,
|
| 505 |
+
'executed_k_per_iter': 256.0,
|
| 506 |
+
'vs_nominal': 1.0},
|
| 507 |
+
'fp8_e5m2': {'calibration_linear': True,
|
| 508 |
+
'executed_k_per_iter': 256.0,
|
| 509 |
+
'vs_nominal': 1.0},
|
| 510 |
+
'int8': {'calibration_linear': True,
|
| 511 |
+
'executed_k_per_iter': 64.0,
|
| 512 |
'vs_nominal': 1.0},
|
| 513 |
+
'tf32': {'calibration_linear': True,
|
| 514 |
+
'executed_k_per_iter': 32.0,
|
| 515 |
'vs_nominal': 1.0}}},
|
| 516 |
+
'mma_fed': {'clock_ghz': 2.705,
|
| 517 |
+
'confidence': 'high',
|
| 518 |
+
'method': 'm16n16k16 fp16 '
|
| 519 |
+
'tiles fed from an '
|
| 520 |
+
'L2-resident buffer '
|
| 521 |
+
'through cp.async '
|
| 522 |
+
'double-buffered '
|
| 523 |
+
'shared memory; '
|
| 524 |
+
'accumulator-verified',
|
| 525 |
+
'note': 'the achieved '
|
| 526 |
+
'counterpart to the '
|
| 527 |
+
'register peak: '
|
| 528 |
+
'staging exposed, one '
|
| 529 |
+
'dependent '
|
| 530 |
+
'accumulator chain '
|
| 531 |
+
'per warp',
|
| 532 |
+
'units': 'TFLOP/s',
|
| 533 |
+
'value': 85.2},
|
| 534 |
+
'occupancy_blocks_per_sm': {'clock_ghz': 2.705,
|
| 535 |
'confidence': 'high',
|
| 536 |
'method': 'occupancy '
|
| 537 |
'API '
|
|
|
|
| 560 |
'16KB': 3,
|
| 561 |
'32KB': 3,
|
| 562 |
'48KB': 2}}},
|
| 563 |
+
'occupancy_knees_fma_tflops': {'clock_ghz': 2.705,
|
| 564 |
'confidence': 'high',
|
| 565 |
'method': 'a '
|
| 566 |
'single '
|
|
|
|
| 580 |
'fixed '
|
| 581 |
'total '
|
| 582 |
'work; '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
'the '
|
| 584 |
'cliff '
|
| 585 |
'marks '
|
|
|
|
| 597 |
'above',
|
| 598 |
'units': 'TFLOP/s',
|
| 599 |
'value': {'t1024': {'0KB': 75.5,
|
| 600 |
+
'32KB': 73.71,
|
| 601 |
+
'48KB': 72.69},
|
| 602 |
+
't128': {'0KB': 79.72,
|
| 603 |
'32KB': 60.31,
|
| 604 |
+
'48KB': 45.81},
|
| 605 |
+
't256': {'0KB': 78.79,
|
| 606 |
+
'32KB': 78.85,
|
| 607 |
+
'48KB': 72.97}}},
|
| 608 |
+
'smem_bw': {'clock_ghz': 2.705,
|
| 609 |
'confidence': 'medium',
|
| 610 |
'method': 'block-resident '
|
| 611 |
'read+write '
|
|
|
|
| 614 |
'aggregated across '
|
| 615 |
'SMs',
|
| 616 |
'note': 'bank-conflict-light '
|
| 617 |
+
'access pattern; the '
|
| 618 |
+
'stride sweep below '
|
| 619 |
+
'prices the '
|
| 620 |
+
'conflicted case',
|
| 621 |
'units': 'GB/s',
|
| 622 |
+
'value': 22244.0},
|
| 623 |
+
'smem_conflict_penalty': {'clock_ghz': 2.705,
|
| 624 |
+
'confidence': 'high',
|
| 625 |
+
'method': 'dependent '
|
| 626 |
+
'read-FMA-write '
|
| 627 |
+
'per '
|
| 628 |
+
'thread '
|
| 629 |
+
'at '
|
| 630 |
+
'warp-lane '
|
| 631 |
+
'bank '
|
| 632 |
+
'distance '
|
| 633 |
+
'`stride`; '
|
| 634 |
+
'stride '
|
| 635 |
+
'32 '
|
| 636 |
+
'puts '
|
| 637 |
+
'every '
|
| 638 |
+
'lane '
|
| 639 |
+
'in '
|
| 640 |
+
'one '
|
| 641 |
+
'bank',
|
| 642 |
+
'note': 'the '
|
| 643 |
+
'direct '
|
| 644 |
+
'conflict '
|
| 645 |
+
'measurement '
|
| 646 |
+
'that '
|
| 647 |
+
'disambiguates '
|
| 648 |
+
'a '
|
| 649 |
+
'bank-conflict '
|
| 650 |
+
'signature '
|
| 651 |
+
'from '
|
| 652 |
+
'an ILP '
|
| 653 |
+
'stall',
|
| 654 |
+
'units': 'x '
|
| 655 |
+
'(time '
|
| 656 |
+
'ratio '
|
| 657 |
+
'vs '
|
| 658 |
+
'stride '
|
| 659 |
+
'1)',
|
| 660 |
+
'value': {'stride_1': 1.0,
|
| 661 |
+
'stride_16': 7.13,
|
| 662 |
+
'stride_2': 1.07,
|
| 663 |
+
'stride_32': 14.08,
|
| 664 |
+
'stride_4': 2.04,
|
| 665 |
+
'stride_8': 3.91}},
|
| 666 |
+
'transfer_bw': {'clock_ghz': 2.705,
|
| 667 |
'confidence': 'high',
|
| 668 |
'method': '256 MB torch '
|
| 669 |
'copies, '
|
|
|
|
| 675 |
'direction of '
|
| 676 |
'traffic',
|
| 677 |
'units': 'GB/s',
|
| 678 |
+
'value': {'d2d': 324.0,
|
| 679 |
+
'd2h_pageable': 7.8,
|
| 680 |
+
'd2h_pinned': 26.3,
|
| 681 |
+
'h2d_pageable': 7.6,
|
| 682 |
'h2d_pinned': 24.0}}}}
|
build/torch213-cxx11-cu132-x86_64-linux/metadata.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 7 |
"backend": {
|
| 8 |
"type": "cuda",
|
| 9 |
"archs": [
|
|
@@ -18,21 +19,20 @@
|
|
| 18 |
"digest": {
|
| 19 |
"algorithm": "sha256",
|
| 20 |
"files": {
|
| 21 |
-
"__init__.py": "
|
| 22 |
-
"
|
| 23 |
-
"_ops.py": "
|
| 24 |
-
"_pinned.py": "
|
| 25 |
-
"metakernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"provenance": {
|
| 29 |
"kernel-builder": {
|
| 30 |
"version": "0.17.0-dev0",
|
| 31 |
-
"sha": "
|
| 32 |
"dirty": false
|
| 33 |
},
|
| 34 |
"kernel": {
|
| 35 |
-
"sha": "
|
| 36 |
"dirty": false
|
| 37 |
}
|
| 38 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "metakernel",
|
| 3 |
+
"id": "_metakernel_cuda_0b9de42",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
| 7 |
+
"kernel-depends": [],
|
| 8 |
"backend": {
|
| 9 |
"type": "cuda",
|
| 10 |
"archs": [
|
|
|
|
| 19 |
"digest": {
|
| 20 |
"algorithm": "sha256",
|
| 21 |
"files": {
|
| 22 |
+
"__init__.py": "lDQ+fJ0JnI3sQGR3UMnDovBbG4ciYBsaFjmgYdKDTKI=",
|
| 23 |
+
"_metakernel_cuda_0b9de42.abi3.so": "gWGz/krb7I0i7P92UTCta5iT7MpGseZcCTmNdjdQtZI=",
|
| 24 |
+
"_ops.py": "x88Bkm1nxRli0nIu2u4ztaJopS1IAZWdN9LZPfspJdo=",
|
| 25 |
+
"_pinned.py": "UyzMniAjfVMIrIbbrSq92KKo5dX5l/GwV3pMVA+eNtM="
|
|
|
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"provenance": {
|
| 29 |
"kernel-builder": {
|
| 30 |
"version": "0.17.0-dev0",
|
| 31 |
+
"sha": "2c40e10d3da1392245ee81bd05b55fb3103b2b34",
|
| 32 |
"dirty": false
|
| 33 |
},
|
| 34 |
"kernel": {
|
| 35 |
+
"sha": "0b9de42fee8a1e52593f2f1f58460c0acc93d7e8",
|
| 36 |
"dirty": false
|
| 37 |
}
|
| 38 |
}
|
build/torch213-cxx11-cu132-x86_64-linux/metakernel/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 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")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|