hotkernel 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hotkernel/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """hotkernel: find the hot kernel, cool it down.
2
+
3
+ An MCP server that gives coding agents (Claude Code, Codex, DeepSeek Harness, Cursor)
4
+ hands on NVIDIA's profiling and disassembly tools, plus the data needed to run an
5
+ overnight kernel-speedrun loop on the GPU you own.
6
+ """
7
+
8
+ __version__ = "0.1.0"
hotkernel/gpus.py ADDED
@@ -0,0 +1,159 @@
1
+ """Roofline constants for consumer and datacenter NVIDIA GPUs.
2
+
3
+ Every number here must be traceable to a vendor document. Dense throughput with
4
+ FP32 accumulate is used for BF16/FP16 on GeForce parts because GeForce halves
5
+ tensor throughput when accumulating in FP32 (NVIDIA RTX Blackwell whitepaper,
6
+ Appendix A), which is what training and most inference kernels actually use.
7
+
8
+ Fields:
9
+ bf16_tflops dense BF16 tensor-core TFLOPS, FP32 accumulate
10
+ fp8_tflops dense FP8 tensor-core TFLOPS, FP32 accumulate (None if unsupported)
11
+ fp32_tflops CUDA-core FP32 TFLOPS
12
+ mem_gbs memory bandwidth, GB/s
13
+ mem_gb memory size, GB
14
+ smem_kb_block max shared memory per block, KB
15
+ tdp_w board power, W
16
+ source URL of the document the numbers come from
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import asdict, dataclass
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class GpuSpec:
26
+ name: str
27
+ arch: str
28
+ sm: str
29
+ bf16_tflops: float
30
+ fp8_tflops: float | None
31
+ fp32_tflops: float
32
+ mem_gbs: float
33
+ mem_gb: float
34
+ smem_kb_block: int
35
+ tdp_w: int
36
+ source: str
37
+
38
+ def as_dict(self) -> dict:
39
+ return asdict(self)
40
+
41
+
42
+ _BLACKWELL_WP = (
43
+ "https://images.nvidia.com/aem-dam/Solutions/geforce/blackwell/"
44
+ "nvidia-rtx-blackwell-gpu-architecture.pdf"
45
+ )
46
+ _ADA_WP = "https://images.nvidia.com/aem-dam/Solutions/geforce/ada/nvidia-ada-gpu-architecture.pdf"
47
+ _AMPERE_WP = "https://images.nvidia.com/aem-dam/en-zz/Solutions/geforce/ampere/pdf/NVIDIA-ampere-GA102-GPU-Architecture-Whitepaper-V1.pdf"
48
+ _H100_DS = "https://www.nvidia.com/en-us/data-center/h100/" # spec table; datasheet PDF is gated
49
+
50
+ # Verified 2026-10-14 against the cited documents (page numbers are of the PDF as published):
51
+ # rtx5090 Blackwell whitepaper, Appendix A, Table 3 (pp. 46-48): FP32 104.8; BF16 Tensor with
52
+ # FP32 accumulate 209.5 (419 sparse); FP8 Tensor with FP32 accumulate 419 (838 sparse);
53
+ # 1792 GB/s; 32 GB GDDR7; TGP 575 W.
54
+ # rtx4090 Ada whitepaper, Appendix A, Table 2 (pp. 29-31): FP32 82.6; BF16 Tensor FP32-acc 165.2
55
+ # (330.4 sparse); FP8 Tensor FP32-acc 330.3 (660.6 sparse); 1008 GB/s; 24 GB; TGP 450 W.
56
+ # rtx3090 Ampere GA102 whitepaper, Appendix A, Table 9 (pp. 44-45): FP32 35.6; BF16 Tensor
57
+ # FP32-acc 71 (142 sparse); no FP8; 936 GB/s; 24 GB; TGP 350 W.
58
+ # h100sxm nvidia.com/en-us/data-center/h100 spec table (H100 SXM column, "with sparsity" rows
59
+ # halved): FP32 67; BF16 1979 sparse -> 989 dense; FP8 3958 sparse -> 1979 dense;
60
+ # 3.35 TB/s; 80 GB; TDP up to 700 W.
61
+ # smem_kb_block is the opt-in maximum dynamic shared memory per block from the CUDA C
62
+ # Programming Guide table "Technical Specifications per Compute Capability" (99 KB
63
+ # for cc 8.6/8.9/12.0, 227 KB for cc 9.0). The 12.0 value was also measured on an
64
+ # RTX 5090: torch.cuda.get_device_properties(0).shared_memory_per_block_optin = 101376 B.
65
+ SPECS: dict[str, GpuSpec] = {
66
+ "rtx5090": GpuSpec(
67
+ name="NVIDIA GeForce RTX 5090", arch="Blackwell (GB202)", sm="sm_120",
68
+ bf16_tflops=209.5, fp8_tflops=419.0, fp32_tflops=104.8,
69
+ mem_gbs=1792.0, mem_gb=32.0, smem_kb_block=99, tdp_w=575,
70
+ source=_BLACKWELL_WP,
71
+ ),
72
+ "rtx4090": GpuSpec(
73
+ name="NVIDIA GeForce RTX 4090", arch="Ada (AD102)", sm="sm_89",
74
+ bf16_tflops=165.2, fp8_tflops=330.3, fp32_tflops=82.6,
75
+ mem_gbs=1008.0, mem_gb=24.0, smem_kb_block=99, tdp_w=450,
76
+ source=_ADA_WP,
77
+ ),
78
+ "rtx3090": GpuSpec(
79
+ name="NVIDIA GeForce RTX 3090", arch="Ampere (GA102)", sm="sm_86",
80
+ bf16_tflops=71.0, fp8_tflops=None, fp32_tflops=35.6,
81
+ mem_gbs=936.0, mem_gb=24.0, smem_kb_block=99, tdp_w=350,
82
+ source=_AMPERE_WP,
83
+ ),
84
+ "h100sxm": GpuSpec(
85
+ name="NVIDIA H100 SXM", arch="Hopper (GH100)", sm="sm_90",
86
+ bf16_tflops=989.0, fp8_tflops=1979.0, fp32_tflops=67.0,
87
+ mem_gbs=3350.0, mem_gb=80.0, smem_kb_block=227, tdp_w=700,
88
+ source=_H100_DS,
89
+ ),
90
+ }
91
+
92
+ _ALIASES = {
93
+ "nvidia geforce rtx 5090": "rtx5090",
94
+ "geforce rtx 5090": "rtx5090",
95
+ "rtx 5090": "rtx5090",
96
+ "5090": "rtx5090",
97
+ "nvidia geforce rtx 4090": "rtx4090",
98
+ "rtx 4090": "rtx4090",
99
+ "4090": "rtx4090",
100
+ "nvidia geforce rtx 3090": "rtx3090",
101
+ "rtx 3090": "rtx3090",
102
+ "3090": "rtx3090",
103
+ "nvidia h100 80gb hbm3": "h100sxm",
104
+ "h100": "h100sxm",
105
+ }
106
+
107
+
108
+ def lookup(name_or_key: str) -> GpuSpec | None:
109
+ key = name_or_key.strip().lower()
110
+ key = _ALIASES.get(key, key.replace(" ", ""))
111
+ return SPECS.get(key)
112
+
113
+
114
+ def roofline(spec: GpuSpec, flops: float | None = None, bytes_: float | None = None,
115
+ dtype: str = "bf16", measured_mem_gbs: float | None = None) -> dict:
116
+ """Return the ridge point and, if flops/bytes are given, where a kernel sits.
117
+
118
+ arithmetic intensity (AI) = flops / bytes. Below the ridge the kernel is memory bound,
119
+ above it compute bound. attainable = min(peak_compute, AI * bandwidth).
120
+ measured_mem_gbs: the bandwidth a streaming copy actually reaches on this card under the
121
+ repo's timer (examples/copy_calibration.py); GDDR parts stream at 80-90% of the spec sheet,
122
+ so the *_measured floors are the ones a kernel can get within 10% of.
123
+ """
124
+ peak = {"bf16": spec.bf16_tflops, "fp16": spec.bf16_tflops,
125
+ "fp8": spec.fp8_tflops, "fp32": spec.fp32_tflops}.get(dtype)
126
+ if peak is None:
127
+ raise ValueError(f"{spec.name} has no {dtype} tensor-core peak in the table")
128
+ ridge_ai = (peak * 1e12) / (spec.mem_gbs * 1e9)
129
+ out = {
130
+ "gpu": spec.name, "dtype": dtype,
131
+ "peak_tflops": peak, "mem_gbs": spec.mem_gbs,
132
+ "ridge_arithmetic_intensity_flop_per_byte": round(ridge_ai, 2),
133
+ "source": spec.source,
134
+ }
135
+ if flops is not None and bytes_ is not None and bytes_ > 0:
136
+ ai = flops / bytes_
137
+ attainable_tflops = min(peak, ai * spec.mem_gbs / 1e3)
138
+ memory_floor_ms = bytes_ / (spec.mem_gbs * 1e9) * 1e3
139
+ compute_floor_ms = flops / (peak * 1e12) * 1e3
140
+ out.update({
141
+ "kernel_arithmetic_intensity": round(ai, 3),
142
+ "bound": "memory" if ai < ridge_ai else "compute",
143
+ "attainable_tflops": round(attainable_tflops, 2),
144
+ # What the loop actually compares against: the best possible time for this
145
+ # traffic and work on this card. Within 10% of it is the stop condition.
146
+ "memory_floor_ms": round(memory_floor_ms, 5),
147
+ "compute_floor_ms": round(compute_floor_ms, 5),
148
+ "attainable_ms": round(max(memory_floor_ms, compute_floor_ms), 5),
149
+ })
150
+ if measured_mem_gbs:
151
+ floor_measured = bytes_ / (measured_mem_gbs * 1e9) * 1e3
152
+ out.update({
153
+ "measured_mem_gbs": measured_mem_gbs,
154
+ "memory_floor_ms_measured": round(floor_measured, 5),
155
+ "attainable_ms_measured": round(max(floor_measured, compute_floor_ms), 5),
156
+ "measured_note": "floor from the bandwidth a streaming copy reaches under the "
157
+ "repo's timer; this is the number to be within 10% of",
158
+ })
159
+ return out