nbxtensor 0.0.1__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.
Files changed (291) hide show
  1. nbxtensor/THIRD_PARTY_NOTICES.md +287 -0
  2. nbxtensor/__init__.py +17 -0
  3. nbxtensor/backend/__init__.py +1 -0
  4. nbxtensor/backend/capabilities.py +207 -0
  5. nbxtensor/backend/cpu/__init__.py +1 -0
  6. nbxtensor/backend/cpu/blas.py +102 -0
  7. nbxtensor/backend/cpu/pool.py +204 -0
  8. nbxtensor/backend/cpu_backend.py +162 -0
  9. nbxtensor/backend/device_facts.py +105 -0
  10. nbxtensor/backend/device_files.py +129 -0
  11. nbxtensor/backend/device_names.py +18 -0
  12. nbxtensor/backend/devices/amd/cdna.yml +228 -0
  13. nbxtensor/backend/devices/amd/cdna2.yml +228 -0
  14. nbxtensor/backend/devices/amd/cdna3.yml +227 -0
  15. nbxtensor/backend/devices/apple/apple_m1.yml +266 -0
  16. nbxtensor/backend/devices/apple/apple_m1_max.yml +266 -0
  17. nbxtensor/backend/devices/apple/apple_m1_pro.yml +266 -0
  18. nbxtensor/backend/devices/apple/apple_m1_ultra.yml +266 -0
  19. nbxtensor/backend/devices/apple/apple_m2.yml +266 -0
  20. nbxtensor/backend/devices/apple/apple_m2_max.yml +266 -0
  21. nbxtensor/backend/devices/apple/apple_m2_pro.yml +266 -0
  22. nbxtensor/backend/devices/apple/apple_m2_ultra.yml +266 -0
  23. nbxtensor/backend/devices/apple/apple_m3.yml +266 -0
  24. nbxtensor/backend/devices/apple/apple_m3_max.yml +266 -0
  25. nbxtensor/backend/devices/apple/apple_m3_pro.yml +266 -0
  26. nbxtensor/backend/devices/apple/apple_m3_ultra.yml +266 -0
  27. nbxtensor/backend/devices/apple/apple_m4.yml +266 -0
  28. nbxtensor/backend/devices/apple/apple_m4_max.yml +266 -0
  29. nbxtensor/backend/devices/apple/apple_m4_pro.yml +308 -0
  30. nbxtensor/backend/devices/apple/apple_m5.yml +269 -0
  31. nbxtensor/backend/devices/apple/apple_m5_max.yml +269 -0
  32. nbxtensor/backend/devices/apple/apple_m5_pro.yml +269 -0
  33. nbxtensor/backend/devices/apple/apple_m5_ultra.yml +269 -0
  34. nbxtensor/backend/devices/apple/apple_m6.yml +269 -0
  35. nbxtensor/backend/devices/apple/apple_silicon.yml +263 -0
  36. nbxtensor/backend/devices/nvidia/ampere.yml +214 -0
  37. nbxtensor/backend/devices/nvidia/hopper.yml +213 -0
  38. nbxtensor/backend/devices/nvidia/volta.yml +411 -0
  39. nbxtensor/backend/faults.py +151 -0
  40. nbxtensor/backend/host_memory.py +185 -0
  41. nbxtensor/backend/launcher.py +2350 -0
  42. nbxtensor/backend/launcher_contract.py +429 -0
  43. nbxtensor/backend/metal_backend.py +661 -0
  44. nbxtensor/backend/metal_device.py +1274 -0
  45. nbxtensor/backend/process_memory.py +73 -0
  46. nbxtensor/backend/replay_graph.py +322 -0
  47. nbxtensor/backend/runtime.py +409 -0
  48. nbxtensor/backend/triton_ext_driver.py +735 -0
  49. nbxtensor/dtype/__init__.py +1 -0
  50. nbxtensor/dtype/convert.py +107 -0
  51. nbxtensor/dtype/dtypes.yml +70 -0
  52. nbxtensor/dtype/itemsize.py +166 -0
  53. nbxtensor/dtype/nbx_dtype.py +72 -0
  54. nbxtensor/dtype/operands.py +158 -0
  55. nbxtensor/dtype/outputs.py +64 -0
  56. nbxtensor/dtype/policy.py +155 -0
  57. nbxtensor/dtype/policy.yml +51 -0
  58. nbxtensor/dtype/promotion.py +112 -0
  59. nbxtensor/host.py +55 -0
  60. nbxtensor/io/__init__.py +1 -0
  61. nbxtensor/io/read_rate.py +197 -0
  62. nbxtensor/io/safetensors.py +314 -0
  63. nbxtensor/memory/__init__.py +9 -0
  64. nbxtensor/memory/allocator.py +1988 -0
  65. nbxtensor/memory/arena.py +75 -0
  66. nbxtensor/memory/device_memory.py +150 -0
  67. nbxtensor/memory/device_transfer.py +154 -0
  68. nbxtensor/memory/layout.py +43 -0
  69. nbxtensor/memory/memory_pool.py +191 -0
  70. nbxtensor/memory/metal.py +96 -0
  71. nbxtensor/memory/oom_advice.py +91 -0
  72. nbxtensor/memory/pointer_tables.py +323 -0
  73. nbxtensor/memory/pool.py +507 -0
  74. nbxtensor/memory/shadow.py +43 -0
  75. nbxtensor/memory/slab.py +273 -0
  76. nbxtensor/memory/trace_replay.py +122 -0
  77. nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +13610 -0
  78. nbxtensor/memory/vmm.py +118 -0
  79. nbxtensor/ops/__init__.py +1 -0
  80. nbxtensor/ops/_autotune_policy.py +90 -0
  81. nbxtensor/ops/_common.py +32 -0
  82. nbxtensor/ops/_configs.py +1041 -0
  83. nbxtensor/ops/_cost.py +231 -0
  84. nbxtensor/ops/abs.py +17 -0
  85. nbxtensor/ops/adaptive_avg_pool2d.py +161 -0
  86. nbxtensor/ops/add.py +96 -0
  87. nbxtensor/ops/addcdiv.py +29 -0
  88. nbxtensor/ops/addcmul.py +29 -0
  89. nbxtensor/ops/addmv_op.py +64 -0
  90. nbxtensor/ops/addr.py +68 -0
  91. nbxtensor/ops/all_reduce.py +83 -0
  92. nbxtensor/ops/amax.py +40 -0
  93. nbxtensor/ops/any_reduce.py +83 -0
  94. nbxtensor/ops/arange_op.py +27 -0
  95. nbxtensor/ops/argmax.py +107 -0
  96. nbxtensor/ops/argmin.py +93 -0
  97. nbxtensor/ops/atan2.py +71 -0
  98. nbxtensor/ops/attention_wrappers.py +1648 -0
  99. nbxtensor/ops/autotune_bucket.py +137 -0
  100. nbxtensor/ops/autotune_cache.py +430 -0
  101. nbxtensor/ops/autotune_certified.py +1262 -0
  102. nbxtensor/ops/autotune_refusals.py +380 -0
  103. nbxtensor/ops/autotune_settings.py +58 -0
  104. nbxtensor/ops/avg_pool2d.py +73 -0
  105. nbxtensor/ops/baddbmm_op.py +132 -0
  106. nbxtensor/ops/batch_norm.py +163 -0
  107. nbxtensor/ops/binary_strided.py +196 -0
  108. nbxtensor/ops/bitwise_and.py +26 -0
  109. nbxtensor/ops/bitwise_left_shift.py +47 -0
  110. nbxtensor/ops/bitwise_not.py +24 -0
  111. nbxtensor/ops/bitwise_or.py +26 -0
  112. nbxtensor/ops/bitwise_right_shift.py +47 -0
  113. nbxtensor/ops/bucketize.py +37 -0
  114. nbxtensor/ops/cat_op.py +87 -0
  115. nbxtensor/ops/celu.py +20 -0
  116. nbxtensor/ops/clamp.py +24 -0
  117. nbxtensor/ops/clamp_min.py +22 -0
  118. nbxtensor/ops/conv2d.py +265 -0
  119. nbxtensor/ops/conv2d_m8n8k4.py +165 -0
  120. nbxtensor/ops/conv_depthwise2d.py +154 -0
  121. nbxtensor/ops/conv_transpose2d.py +148 -0
  122. nbxtensor/ops/conv_wrappers.py +1482 -0
  123. nbxtensor/ops/copy.py +18 -0
  124. nbxtensor/ops/copy_op.py +39 -0
  125. nbxtensor/ops/cos.py +17 -0
  126. nbxtensor/ops/count_nonzero.py +64 -0
  127. nbxtensor/ops/cross_entropy.py +67 -0
  128. nbxtensor/ops/cummax.py +109 -0
  129. nbxtensor/ops/cummin.py +105 -0
  130. nbxtensor/ops/cumsum.py +244 -0
  131. nbxtensor/ops/decode_attn_vec.py +162 -0
  132. nbxtensor/ops/depthwise_conv2d.py +132 -0
  133. nbxtensor/ops/dequant_gemv.py +190 -0
  134. nbxtensor/ops/dft.py +67 -0
  135. nbxtensor/ops/dispatch.py +1228 -0
  136. nbxtensor/ops/div.py +75 -0
  137. nbxtensor/ops/dot_op.py +67 -0
  138. nbxtensor/ops/dropout.py +23 -0
  139. nbxtensor/ops/dtype_convert.py +42 -0
  140. nbxtensor/ops/elementwise_launch.py +81 -0
  141. nbxtensor/ops/elementwise_wrappers.py +2315 -0
  142. nbxtensor/ops/elu.py +20 -0
  143. nbxtensor/ops/embedding.py +69 -0
  144. nbxtensor/ops/eq.py +32 -0
  145. nbxtensor/ops/erf.py +18 -0
  146. nbxtensor/ops/exp.py +17 -0
  147. nbxtensor/ops/exp2.py +18 -0
  148. nbxtensor/ops/fft_op.py +125 -0
  149. nbxtensor/ops/fill_op.py +20 -0
  150. nbxtensor/ops/flash_attention.py +307 -0
  151. nbxtensor/ops/flash_attention_m8n8k4.py +384 -0
  152. nbxtensor/ops/flash_decode.py +78 -0
  153. nbxtensor/ops/flip_op.py +60 -0
  154. nbxtensor/ops/floor.py +90 -0
  155. nbxtensor/ops/full_op.py +24 -0
  156. nbxtensor/ops/fused_moe.py +417 -0
  157. nbxtensor/ops/fused_moe_m8n8k4.py +99 -0
  158. nbxtensor/ops/fused_upsample_conv.py +413 -0
  159. nbxtensor/ops/gather_op.py +52 -0
  160. nbxtensor/ops/ge.py +32 -0
  161. nbxtensor/ops/gelu.py +31 -0
  162. nbxtensor/ops/gemv_vec.py +80 -0
  163. nbxtensor/ops/glu.py +43 -0
  164. nbxtensor/ops/grid_sampler.py +145 -0
  165. nbxtensor/ops/group_norm_tiled.py +67 -0
  166. nbxtensor/ops/groupnorm.py +123 -0
  167. nbxtensor/ops/gt.py +32 -0
  168. nbxtensor/ops/hardsigmoid.py +18 -0
  169. nbxtensor/ops/hardswish.py +19 -0
  170. nbxtensor/ops/im2col.py +57 -0
  171. nbxtensor/ops/index_add.py +95 -0
  172. nbxtensor/ops/index_put_op.py +92 -0
  173. nbxtensor/ops/index_select.py +121 -0
  174. nbxtensor/ops/index_wrappers.py +770 -0
  175. nbxtensor/ops/isfinite.py +20 -0
  176. nbxtensor/ops/isinf.py +20 -0
  177. nbxtensor/ops/isnan.py +18 -0
  178. nbxtensor/ops/launch_keys.py +1054 -0
  179. nbxtensor/ops/launch_observer.py +54 -0
  180. nbxtensor/ops/layernorm.py +71 -0
  181. nbxtensor/ops/le.py +35 -0
  182. nbxtensor/ops/leaky_relu.py +19 -0
  183. nbxtensor/ops/lerp.py +76 -0
  184. nbxtensor/ops/linalg_wrappers.py +1370 -0
  185. nbxtensor/ops/linspace_op.py +47 -0
  186. nbxtensor/ops/log.py +17 -0
  187. nbxtensor/ops/log_sigmoid.py +23 -0
  188. nbxtensor/ops/logical_and.py +26 -0
  189. nbxtensor/ops/logical_not.py +24 -0
  190. nbxtensor/ops/logical_or.py +26 -0
  191. nbxtensor/ops/logical_xor.py +37 -0
  192. nbxtensor/ops/loss_wrappers.py +282 -0
  193. nbxtensor/ops/lt.py +32 -0
  194. nbxtensor/ops/masked_fill.py +21 -0
  195. nbxtensor/ops/matmul.py +377 -0
  196. nbxtensor/ops/matmul_m8n8k4.py +235 -0
  197. nbxtensor/ops/max_pool2d.py +74 -0
  198. nbxtensor/ops/max_pool3d.py +93 -0
  199. nbxtensor/ops/max_reduce.py +91 -0
  200. nbxtensor/ops/maximum.py +18 -0
  201. nbxtensor/ops/mean.py +40 -0
  202. nbxtensor/ops/min_reduce.py +91 -0
  203. nbxtensor/ops/minimum.py +18 -0
  204. nbxtensor/ops/mish.py +22 -0
  205. nbxtensor/ops/mma_m8n8k4.py +152 -0
  206. nbxtensor/ops/moe_align.py +195 -0
  207. nbxtensor/ops/moe_decode_vec.py +374 -0
  208. nbxtensor/ops/moe_device_residency.py +141 -0
  209. nbxtensor/ops/moe_wrappers.py +345 -0
  210. nbxtensor/ops/mse_loss.py +79 -0
  211. nbxtensor/ops/mul.py +51 -0
  212. nbxtensor/ops/nan_to_num.py +23 -0
  213. nbxtensor/ops/ne.py +32 -0
  214. nbxtensor/ops/neg.py +17 -0
  215. nbxtensor/ops/nllloss.py +124 -0
  216. nbxtensor/ops/nonzero.py +33 -0
  217. nbxtensor/ops/nonzero_op.py +62 -0
  218. nbxtensor/ops/norm_wrappers.py +579 -0
  219. nbxtensor/ops/ones_op.py +20 -0
  220. nbxtensor/ops/oracles/__init__.py +6 -0
  221. nbxtensor/ops/oracles/conv2d_fp64.py +450 -0
  222. nbxtensor/ops/pad_op.py +159 -0
  223. nbxtensor/ops/paged_decode_attn.py +215 -0
  224. nbxtensor/ops/pixel_shuffle.py +144 -0
  225. nbxtensor/ops/pixel_unshuffle.py +72 -0
  226. nbxtensor/ops/pow.py +137 -0
  227. nbxtensor/ops/prod.py +81 -0
  228. nbxtensor/ops/rand_op.py +52 -0
  229. nbxtensor/ops/random_wrappers.py +366 -0
  230. nbxtensor/ops/reciprocal.py +17 -0
  231. nbxtensor/ops/reduction_wrappers.py +845 -0
  232. nbxtensor/ops/relu.py +18 -0
  233. nbxtensor/ops/remainder.py +46 -0
  234. nbxtensor/ops/repeat_interleave.py +48 -0
  235. nbxtensor/ops/repeat_op.py +132 -0
  236. nbxtensor/ops/residual_chain.py +374 -0
  237. nbxtensor/ops/rmsnorm.py +68 -0
  238. nbxtensor/ops/rope.py +142 -0
  239. nbxtensor/ops/rsqrt.py +17 -0
  240. nbxtensor/ops/scaled_softmax.py +96 -0
  241. nbxtensor/ops/scatter_op.py +139 -0
  242. nbxtensor/ops/screen_oracle.py +479 -0
  243. nbxtensor/ops/selu.py +22 -0
  244. nbxtensor/ops/shape_wrappers.py +1032 -0
  245. nbxtensor/ops/sigmoid.py +19 -0
  246. nbxtensor/ops/silu.py +27 -0
  247. nbxtensor/ops/sin.py +17 -0
  248. nbxtensor/ops/softmax.py +169 -0
  249. nbxtensor/ops/softplus.py +27 -0
  250. nbxtensor/ops/sort_op.py +304 -0
  251. nbxtensor/ops/spectral_wrappers.py +481 -0
  252. nbxtensor/ops/sqrt.py +17 -0
  253. nbxtensor/ops/stack_op.py +84 -0
  254. nbxtensor/ops/std.py +113 -0
  255. nbxtensor/ops/strided_copy.py +200 -0
  256. nbxtensor/ops/sub.py +35 -0
  257. nbxtensor/ops/sum.py +40 -0
  258. nbxtensor/ops/tan.py +19 -0
  259. nbxtensor/ops/tanh.py +21 -0
  260. nbxtensor/ops/threshold.py +19 -0
  261. nbxtensor/ops/tiling.yml +91 -0
  262. nbxtensor/ops/tiling_policy.py +43 -0
  263. nbxtensor/ops/tiling_sizes.py +370 -0
  264. nbxtensor/ops/topk.py +267 -0
  265. nbxtensor/ops/trace_op.py +64 -0
  266. nbxtensor/ops/tril.py +64 -0
  267. nbxtensor/ops/triu.py +62 -0
  268. nbxtensor/ops/unfold_op.py +37 -0
  269. nbxtensor/ops/unique_op.py +97 -0
  270. nbxtensor/ops/upsample_bilinear2d.py +106 -0
  271. nbxtensor/ops/upsample_nearest2d.py +52 -0
  272. nbxtensor/ops/upsample_nearest3d.py +61 -0
  273. nbxtensor/ops/var.py +136 -0
  274. nbxtensor/ops/vector_norm.py +216 -0
  275. nbxtensor/ops/weight_norm.py +104 -0
  276. nbxtensor/ops/where.py +39 -0
  277. nbxtensor/ops/wrappers.py +825 -0
  278. nbxtensor/ops/zeros_op.py +20 -0
  279. nbxtensor/quant/__init__.py +1 -0
  280. nbxtensor/quant/quantized_tensor.py +187 -0
  281. nbxtensor/random/__init__.py +1 -0
  282. nbxtensor/random/mt_randn.py +154 -0
  283. nbxtensor/random/rng_pin.py +59 -0
  284. nbxtensor/random/rng_stream.py +118 -0
  285. nbxtensor/random/seeded_draw.py +223 -0
  286. nbxtensor/shape/__init__.py +157 -0
  287. nbxtensor/tensor.py +1867 -0
  288. nbxtensor-0.0.1.dist-info/METADATA +379 -0
  289. nbxtensor-0.0.1.dist-info/RECORD +291 -0
  290. nbxtensor-0.0.1.dist-info/WHEEL +4 -0
  291. nbxtensor-0.0.1.dist-info/licenses/LICENSE +189 -0
@@ -0,0 +1,137 @@
1
+ """The bucket of a request-dependent dimension in an autotune key.
2
+
3
+ The owner's decision (2026-09-21): request-dependent dimensions — a prefill's token count,
4
+ a decode's key length, a convolution's spatial extents and batch — are BUCKETED in the
5
+ launcher's key. The bucket selects the configuration; the kernel still runs the true size
6
+ with its masks. Without it a census can only cover the requests it is shown, a 57-token
7
+ prompt meets a key nobody certified, and zero miss at verification can never be true.
8
+
9
+ The ladder is DATA, on the vendor profile (`autotune.buckets`), chosen by measurement the
10
+ way the tile unit was (`tools/bucket_loss.py`, owed-proofs 2026-09-21): on Volta, both
11
+ memory classes, matmul M and the SDPA scores' batched GEMM — exact under 64 (the batched
12
+ GEMM's optimum moves with every size there: up to 16.7 % lost inside a 16-wide bucket),
13
+ then 16-step to 256, 32-step to 1024, 128-step to 8192, 512-step beyond: 0.0 % median and
14
+ maximum loss against the per-size optimum, where powers of two lost up to 26.9 %.
15
+
16
+ A profile that is BOUND but declares no ladder is REFUSED, not quietly bucketed at step 1.
17
+ Answering "exact" there is a silent degradation dressed as a default: it produces a key
18
+ explosion with no error, and it hid for a day that the ladder had been added to
19
+ nvidia/volta.yml alone while all twenty-six other profiles carried none (found 2026-09-22 by
20
+ the schema gate, not by a run). An UNBOUND profile — `{}`, no target matched, the census
21
+ behind its door before `install()` — is a different condition and still keys exact; that one
22
+ is covered by binding the shadow to its profile, not by a refusal here.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ from typing import Any, Dict, List, Optional, Sequence, Tuple
27
+
28
+ #: A ladder is a list of [upper_bound, step] rows in ascending bound order: a value v takes
29
+ #: the smallest multiple of `step` >= v within the first row whose bound is >= v. A step of
30
+ #: 1 is "exact". The last row's bound may be null (open).
31
+ Ladder = Sequence[Tuple[Optional[int], int]]
32
+
33
+
34
+ _PARSED: Dict[str, Ladder] = {}
35
+ _BY_ID: Dict[int, Tuple[Any, Ladder]] = {}
36
+
37
+ #: Dimensions that are NOT a tensor dimension and never take the profile's `default` ladder: an element-wise launch's
38
+ #: element count is classed by the launch-overhead and bandwidth regimes (four classes on volta and apple_m4_pro), and
39
+ #: answered by the dimension ladder it became 27 868 classes on apple_m4_pro (the Mac's census, 2026-10-24 12:26) —
40
+ #: five days of certification for one launch tile. A profile without its own ladder for one of these is refused.
41
+ NO_DEFAULT = frozenset({"elements"})
42
+
43
+
44
+ def parse_ladder(rows: Any) -> Ladder:
45
+ """Parsed once per distinct row list: the key of every launch reads the ladder (32 616
46
+ parses in one chatterbox shadow, 2026-09-22)."""
47
+ # the profile hands the same row list on every call: answered by its identity first (the `repr` key below, built
48
+ # per call, was itself the cost — 2 of 40 decode-step samples of Qwen3-30B-A3B, 2026-10-28); the list is kept so
49
+ # its id is never reused by another
50
+ hit = _BY_ID.get(id(rows))
51
+ if hit is not None and hit[0] is rows:
52
+ return hit[1]
53
+ memo = repr(rows)
54
+ got = _PARSED.get(memo)
55
+ if got is None:
56
+ got = _PARSED[memo] = _parse_ladder(rows)
57
+ _BY_ID[id(rows)] = (rows, got)
58
+ return got
59
+
60
+
61
+ def _parse_ladder(rows: Any) -> Ladder:
62
+ out: List[Tuple[Optional[int], int]] = []
63
+ for r in rows or []:
64
+ if isinstance(r, dict):
65
+ bound, step = r.get("up_to"), r.get("step")
66
+ else:
67
+ bound, step = r[0], r[1]
68
+ if step is None or int(step) < 1:
69
+ raise ValueError(f"ZERO FALLBACK: a bucket ladder row needs a step >= 1: {r!r}")
70
+ out.append((None if bound is None else int(bound), int(step)))
71
+ for i in range(1, len(out)):
72
+ if out[i - 1][0] is None or (out[i][0] is not None and out[i][0] <= out[i - 1][0]):
73
+ raise ValueError(f"ZERO FALLBACK: bucket ladder bounds must ascend and only the last may be open: {rows!r}")
74
+ return out
75
+
76
+
77
+ def bucket(value: int, ladder: Ladder) -> int:
78
+ """The bucket's TOP for `value`: the smallest multiple of the row's step that is >= value."""
79
+ v = int(value)
80
+ if v <= 0:
81
+ return v
82
+ for bound, step in ladder:
83
+ if bound is None or v <= bound:
84
+ return -(-v // step) * step
85
+ bound, step = ladder[-1]
86
+ return -(-v // step) * step
87
+
88
+
89
+ def ladder_for(dim: str, profile: Optional[Dict[str, Any]] = None) -> Ladder:
90
+ """The profile's ladder for `dim` ("M", "N", "batch", "height", "width"), else exact."""
91
+ if profile is None:
92
+ from nbxtensor.ops._configs import active_vendor_profile
93
+ profile = active_vendor_profile()
94
+ bound = bool(profile)
95
+ spec = ((profile or {}).get("autotune") or {}).get("buckets") or {}
96
+ rows = spec.get(dim) if isinstance(spec, dict) else None
97
+ if rows is None and dim in NO_DEFAULT and bound:
98
+ raise RuntimeError(
99
+ f"ZERO FALLBACK: the bound hardware profile {(profile or {}).get('architecture') or '<unnamed>'!r} declares "
100
+ f"no autotune.buckets.{dim} ladder, and {dim!r} never takes the `default` dimension ladder (an element "
101
+ f"count's classes are its launch-overhead and bandwidth regimes, not dimension steps). Add the measured "
102
+ f"ladder, with its source, to its vendor YAML (see nvidia/volta.yml).")
103
+ if rows is None:
104
+ rows = spec.get("default") if isinstance(spec, dict) else None
105
+ if rows:
106
+ return parse_ladder(rows)
107
+ if bound:
108
+ # ZERO FALLBACK: a real profile that forgot the ladder is a defect in the profile,
109
+ # and keying exact would hide it behind a plausible-looking answer.
110
+ raise RuntimeError(
111
+ "ZERO FALLBACK: the bound hardware profile "
112
+ f"{(profile or {}).get('architecture') or '<unnamed>'!r} declares no "
113
+ f"autotune.buckets ladder, so dimension {dim!r} has no bucket.\n"
114
+ " Add `autotune.buckets.default` to its vendor YAML (see nvidia/volta.yml), "
115
+ "or the census keys this dimension EXACTLY and every request length becomes "
116
+ "its own key.\n"
117
+ " A step of 1 is a legitimate ladder and must be written down as one."
118
+ )
119
+ return [(None, 1)] # no target bound: the census's own door, unchanged
120
+
121
+
122
+ def class_floor(dim: str, top: int, profile: Optional[Dict[str, Any]] = None) -> int:
123
+ """The fewest `dim` that form the class whose top is `top` (`bucket_of`'s inverse floor): the row holding `top`,
124
+ one past the class below it (its top minus the row's step) or past the previous row's bound."""
125
+ t = int(top)
126
+ if t <= 0:
127
+ return t
128
+ prev = 0
129
+ for bound, step in ladder_for(dim, profile):
130
+ if bound is None or t <= bound:
131
+ return max(prev + 1, t - step + 1)
132
+ prev = bound
133
+ return max(prev + 1, t - ladder_for(dim, profile)[-1][1] + 1)
134
+
135
+
136
+ def bucket_of(dim: str, value: int, profile: Optional[Dict[str, Any]] = None) -> int:
137
+ return bucket(value, ladder_for(dim, profile))
@@ -0,0 +1,430 @@
1
+ """E2-full — persistable autotune-config artifact (scoping §Phase 4a.4).
2
+
3
+ Triton's own disk cache (`cache_results=True`) persists autotune
4
+ selections keyed by SOURCE HASH — any kernel-file edit invalidates it
5
+ and the next run re-benches (the battery kernel-edit false-RED class,
6
+ and a run-to-run timing-variance surface). This module captures the
7
+ SELECTED configs keyed by OUR fingerprint — (kernel qualname,
8
+ autotune-key literal, arch) — and seeds them back into the Autotuner
9
+ in-memory caches, immune to source-hash invalidation:
10
+
11
+ capture() after a warmed pass (the replay recording pass), walk the
12
+ five sanctioned Autotuner kernels (mm/bmm/addmm/conv2d/
13
+ depthwise — the Phase 1.5 scope) and merge their selected
14
+ configs into the artifact.
15
+ seed() before execution, insert stored configs into each
16
+ Autotuner's cache: a hit means run() never benches.
17
+
18
+ R33: imports triton + stdlib only. The artifact is an optimization,
19
+ never a failure source — every I/O error degrades to "no seed".
20
+ Storage: ~/.neurobrix/replay_cache/autotune_configs_<arch>.json.
21
+ """
22
+
23
+ import ast
24
+ import json
25
+ import os
26
+ from typing import Dict, Iterator, Optional, Tuple
27
+
28
+ # The machine's replay cache (the producer's accumulation across models, and
29
+ # the correctness screen's exclusions). NEUROBRIX_REPLAY_CACHE relocates it —
30
+ # a gate that needs a truly cold sweep per arm gives every arm its own, else
31
+ # sweep mode seeds the autotuners from here and no arm sweeps (2026-09-06:
32
+ # a screened arm reported "checked 0 key(s)" for exactly this reason).
33
+ def _dir() -> str:
34
+ """Where the local replay cache lives, read WHEN USED.
35
+
36
+ This was a module global computed at import time. A value frozen at import
37
+ from an environment variable is the same class of defect as a literal
38
+ standing in for a runtime value: whoever imports the module first decides it
39
+ for the whole process. Measured 2026-09-17 — a test redirecting
40
+ NEUROBRIX_REPLAY_CACHE to a tmp path imported this module inside its own
41
+ body, froze the tmp path, and a later test in the same process asserting on
42
+ the real directory failed. monkeypatch restored the env; it could not
43
+ restore a constant already computed from it.
44
+ """
45
+ return (os.environ.get("NEUROBRIX_REPLAY_CACHE")
46
+ or os.path.join(os.path.expanduser("~"), ".neurobrix", "replay_cache"))
47
+
48
+
49
+ #: The directory as it stood AT IMPORT. Kept as a real attribute because
50
+ #: `importlib.reload` is how the relocation test observes it, and a module
51
+ #: __getattr__ does not survive that cleanly. Everything INSIDE this module
52
+ #: calls `_dir()` instead, so a process that changes the environment after
53
+ #: import writes where the environment now says — which is what a test
54
+ #: redirecting it to a tmp path needs, and what a later test reading the real
55
+ #: directory needs too.
56
+ _DIR = _dir()
57
+
58
+ # The sanctioned autotune surface (Phase 1.5 doctrine: mm/bmm/addmm/
59
+ # conv2d only) — explicit list, not a gc walk. A new autotuned kernel
60
+ # is added here the day its autotune exception is granted. Supervisor
61
+ # ruling 2026-08-16: NO autotuner lives outside this regime (the one
62
+ # historical candidate outside it, the FlagGems kernels/utils remnant,
63
+ # was dead code and was removed 2026-08-17). Since the owner directive of
64
+ # 2026-09-06 the regime is the CERTIFIED DIRECTORY (`kernels/
65
+ # autotune_certified.py`, `config/autotune/<vendor>/<profile>/`): a shape it
66
+ # holds is applied at load; a shape it lacks sweeps at runtime with the
67
+ # consensus screen and lands HERE, the machine's local replay cache.
68
+ _KERNEL_SITES = (
69
+ ("nbxtensor.ops.matmul", "matmul_kernel"),
70
+ ("nbxtensor.ops.matmul", "addmm_kernel"),
71
+ ("nbxtensor.ops.baddbmm_op", "baddbmm_kernel"),
72
+ ("nbxtensor.ops.conv2d", "conv2d_forward_kernel"),
73
+ ("nbxtensor.ops.depthwise_conv2d", "depthwise_conv2d_kernel"),
74
+ # workstream 1: certified per key like the five above (the softmax's module is imported with the wrappers; the
75
+ # matrix-unit flash's only when an attention takes it — a certification before any attention found no tuner)
76
+ ("nbxtensor.ops.softmax", "softmax_forward_kernel"),
77
+ ("nbxtensor.ops.flash_attention_m8n8k4", "flash_attention_m8n8k4_kernel"),
78
+ # C3: the tl.dot flash's tile, certified where the profile declares no hand table (`sdpa_thresholds`)
79
+ ("nbxtensor.ops.flash_attention", "flash_attention_certified_kernel"),
80
+ )
81
+
82
+
83
+ def _arch_fingerprint() -> Optional[str]:
84
+ """None when the driver query fails — no fingerprint means NO
85
+ artifact (capture/seed no-op), never a shared cross-arch bucket
86
+ (an sm_80 config seeded onto sm_70 fails at compile inside a user
87
+ run)."""
88
+ try:
89
+ from nbxtensor.backend.launcher import target as _nbx_target # engine data, no driver probe (R33)
90
+ target = _nbx_target()
91
+ return f"{target.backend}-{target.arch}"
92
+ except Exception:
93
+ return None
94
+
95
+
96
+ def _running_generator() -> Optional[str]:
97
+ """The code generator about to run, as the certified directory's proofs record it
98
+ (`kernels.autotune_certified.running_generator`: the Triton distribution, the target, and the
99
+ out-of-tree backend's source hash where one generates the code). None when it cannot be named."""
100
+ try:
101
+ from nbxtensor.ops.autotune_certified import running_generator
102
+ return running_generator()
103
+ except Exception:
104
+ return None
105
+
106
+
107
+ def _generator_tag() -> Optional[str]:
108
+ """A file-safe tag of the running generator: its backend hash's short id where the backend is
109
+ out of tree (`msl-v0.1-42e` -> `42e`), else the whole identity flattened."""
110
+ gen = _running_generator()
111
+ if not gen:
112
+ return None
113
+ import re as _re
114
+ m = _re.search(r"msl-v[\d.]+-([0-9a-f]{3,})", gen) # the proof records the short id (`msl-v0.1-42e`)
115
+ tag = m.group(1) if m else _re.sub(r"[^0-9A-Za-z.]+", "_", gen).strip("_")
116
+ return tag[:40] or None
117
+
118
+
119
+ def _artifact_path() -> Optional[str]:
120
+ """The replay artifact of THIS backend, arch AND code generator. A configuration is a property
121
+ of the compiler that produced it (the certified directory's generator door, 2026-09-06): a
122
+ sweep made under one generator is never seeded under another. Before 2026-09-28 the file was
123
+ named by arch alone, and on the Mac the two "served" gates of the triton-ext bump took 545 of
124
+ their 1 400 key uses from sweeps the retired compiler had made. No generator name, no artifact."""
125
+ arch = _arch_fingerprint()
126
+ if arch is None:
127
+ return None
128
+ gen = _generator_tag()
129
+ if gen is None:
130
+ return None
131
+ return os.path.join(_dir(), f"autotune_configs_{arch}-{gen}.json")
132
+
133
+
134
+ def _autotuners() -> Iterator[Tuple[str, object]]:
135
+ """Every Autotuner the process has loaded from the kernel library — the
136
+ five sanctioned sites first (imported on demand), then every other
137
+ `neurobrix.kernels.ops.*` module already in sys.modules. Since 2026-09-05
138
+ the engine is the only persistence of a sweep (upstream's on-disk cache
139
+ keyed its files by a torch-importing driver probe and was dropped), so
140
+ every tuner's selections are captured, not only the matmul class's."""
141
+ import importlib
142
+ import sys
143
+ from triton.runtime.autotuner import Autotuner
144
+ seen = set()
145
+ for mod_name, attr in _KERNEL_SITES:
146
+ try:
147
+ obj = getattr(importlib.import_module(mod_name), attr, None)
148
+ except Exception:
149
+ continue
150
+ if isinstance(obj, Autotuner):
151
+ seen.add(id(obj))
152
+ yield f"{mod_name}.{attr}", obj
153
+ for mod_name, mod in list(sys.modules.items()):
154
+ # the kernel files: the library's (`nbxtensor.ops`) and the engine's still to move (`neurobrix.kernels.ops`)
155
+ if not mod_name.startswith(("nbxtensor.ops.", "neurobrix.kernels.ops.")) or mod is None:
156
+ continue
157
+ for attr, obj in list(vars(mod).items()):
158
+ if isinstance(obj, Autotuner) and id(obj) not in seen:
159
+ seen.add(id(obj))
160
+ yield f"{mod_name}.{attr}", obj
161
+
162
+
163
+ def _config_to_dict(cfg) -> Dict:
164
+ return {"kwargs": dict(cfg.kwargs), "num_warps": cfg.num_warps,
165
+ "num_stages": cfg.num_stages, "num_ctas": cfg.num_ctas,
166
+ "maxnreg": cfg.maxnreg}
167
+
168
+
169
+ def _config_from_dict(d):
170
+ import triton
171
+ return triton.Config(dict(d["kwargs"]), num_warps=d["num_warps"],
172
+ num_stages=d["num_stages"], num_ctas=d["num_ctas"],
173
+ maxnreg=d.get("maxnreg"))
174
+
175
+
176
+ def _exclusions_path() -> Optional[str]:
177
+ """Where the correctness screen's exclusions are kept, beside the sweep."""
178
+ path = _artifact_path()
179
+ if path is None:
180
+ return None
181
+ return path.replace("autotune_configs_", "autotune_exclusions_")
182
+
183
+
184
+ def record_screen_exclusions(entries) -> int:
185
+ """Persist configs the correctness screen refused, so a later sweep and its readers see them.
186
+
187
+ A config excluded for being WRONG and a config that merely lost on speed
188
+ are indistinguishable in a sweep that records only the winner — and they
189
+ are not the same fact at all. One is a tuning outcome; the other is a
190
+ backend defect with a shape attached.
191
+
192
+ Keyed by kernel, key and config so repeated runs merge instead of
193
+ accumulating duplicates. Best-effort: a sweep that cannot be written must
194
+ never fail a launch, and the exclusions are also printed as they happen.
195
+ """
196
+ path = _exclusions_path()
197
+ if path is None or not entries:
198
+ return 0
199
+ try:
200
+ os.makedirs(_dir(), exist_ok=True)
201
+ stored: Dict[str, Dict] = {}
202
+ try:
203
+ with open(path) as f:
204
+ stored = json.load(f)
205
+ except (OSError, ValueError):
206
+ stored = {}
207
+ added = 0
208
+ for entry in entries:
209
+ entry = dict(entry)
210
+ ident = f"{entry.get('kernel')}::{entry.get('key')!r}::{entry.get('config')}"
211
+ if ident not in stored:
212
+ stored[ident] = entry
213
+ added += 1
214
+ if added:
215
+ with open(path, "w") as f:
216
+ json.dump(stored, f, indent=1, default=str)
217
+ return added
218
+ except OSError:
219
+ return 0
220
+
221
+
222
+ def screen_exclusions() -> Dict[str, Dict]:
223
+ """Everything the screen has refused on this machine."""
224
+ path = _exclusions_path()
225
+ if path is None:
226
+ return {}
227
+ try:
228
+ with open(path) as f:
229
+ return json.load(f)
230
+ except (OSError, ValueError):
231
+ return {}
232
+
233
+
234
+ #: Keys whose config was chosen WITHOUT measurement -- e.g. the bench was
235
+ #: skipped because its arguments alone exceed the machine's available memory,
236
+ #: so timing candidates would have measured the swap and not the kernels. A
237
+ #: choice made without measurement must never be recorded as if measured:
238
+ #: persisted, it would outlive the pressure that forced it and keep deciding
239
+ #: on machines and days it knows nothing about.
240
+ _UNMEASURED: set = set()
241
+
242
+
243
+ def mark_unmeasured(at, key) -> None:
244
+ _UNMEASURED.add((id(at), tuple(key) if isinstance(key, (list, tuple)) else key))
245
+
246
+
247
+ def is_unmeasured(at, key) -> bool:
248
+ return (id(at), tuple(key) if isinstance(key, (list, tuple)) else key) in _UNMEASURED
249
+
250
+
251
+ def capture() -> int:
252
+ """Merge every selected config into the artifact. Returns the
253
+ number of NEW entries written (0 = artifact already covers this
254
+ process's selections)."""
255
+ path = _artifact_path()
256
+ if path is None:
257
+ return 0
258
+ # THE RECORD CARRIES WHAT IT IS. A configuration the screen seated without
259
+ # an oracle is the fastest among candidates nothing verified, and until the
260
+ # owner's ruling of 2026-09-12 it was written here exactly like one an fp64
261
+ # oracle had adjudicated. The engine still runs and the screen still ranks
262
+ # by speed; what it may no longer do is produce a record that reads as a
263
+ # validation. The certified directory is a different path entirely
264
+ # (`config/autotune/`, filled only by `neurobrix autotune certify`), so
265
+ # nothing here can reach it — this is the mention where it IS recorded.
266
+ try:
267
+ from nbxtensor.backend.launcher import adjudicated as _adjudicated, unscreened as _unscreened
268
+ unverified = {(u.kernel, repr(u.key)): u.reason for u in _unscreened()}
269
+ verified = _adjudicated()
270
+ except Exception: # never turn a record into a failure
271
+ unverified, verified = {}, {}
272
+
273
+ entries: Dict[str, Dict] = {}
274
+ skipped_unmeasured = 0
275
+ for qual, at in _autotuners():
276
+ short = qual.rsplit(".", 1)[-1]
277
+ for key, cfg in getattr(at, "cache", {}).items():
278
+ if is_unmeasured(at, key):
279
+ skipped_unmeasured += 1
280
+ continue
281
+ rec = _config_to_dict(cfg)
282
+ if (id(at), key) in _TIMINGS:
283
+ rec["timing"] = _TIMINGS[(id(at), key)]
284
+ adjudicator = verified.get((short, repr(key)))
285
+ if adjudicator is not None:
286
+ rec["screened"] = True
287
+ rec["screened_by"] = adjudicator
288
+ reason = unverified.get((short, repr(key)))
289
+ if reason is not None:
290
+ rec["screened"] = False
291
+ rec["unscreened_reason"] = reason
292
+ rec["provenance"] = ("fastest among candidates nothing "
293
+ "verified — NOT a validated setting")
294
+ # A shape over the screening budget is screened on ROW WINDOWS, and the proof
295
+ # names them: what was verified and what was not must always be readable
296
+ # (owner, 2026-09-23). Recorded for the key whether the windows were adjudicated
297
+ # by the fp64 oracle or, where no windowed oracle covers the kernel, by consensus.
298
+ try:
299
+ from nbxtensor.backend.launcher import screen_windows_of
300
+ win = screen_windows_of(key)
301
+ except Exception: # noqa: BLE001
302
+ win = None
303
+ if win is not None:
304
+ rec["screened"] = True
305
+ rec["screened_on_windows"] = win["windows"]
306
+ rec["screened_by"] = win["adjudicated_by"]
307
+ rec["screen_kept"] = f"{win['kept']} of {win['candidates']}"
308
+ rec["provenance"] = (
309
+ f"verified on {win['windows']} by {win['adjudicated_by']}; the rest of "
310
+ f"the output and the input buffers were NOT compared")
311
+ entries[f"{qual}::{key!r}"] = rec
312
+ if skipped_unmeasured:
313
+ print(f"[AUTOTUNE_CACHE] {skipped_unmeasured} choice(s) made without "
314
+ f"measurement were NOT persisted", flush=True)
315
+ if not entries:
316
+ return 0
317
+ try:
318
+ os.makedirs(_dir(), exist_ok=True)
319
+ stored: Dict[str, Dict] = {}
320
+ try:
321
+ with open(path) as f:
322
+ stored = json.load(f)
323
+ except (OSError, ValueError):
324
+ stored = {}
325
+ new = {k: v for k, v in entries.items() if k not in stored}
326
+ if new:
327
+ stored.update(new)
328
+ with open(path, "w") as f:
329
+ json.dump(stored, f)
330
+ return len(new)
331
+ except OSError:
332
+ return 0
333
+
334
+
335
+ def seed() -> int:
336
+ """Insert stored configs into the Autotuner caches. Returns the
337
+ number of entries seeded.
338
+
339
+ MEMBERSHIP GATE (staleness safety): a stored config is seeded ONLY
340
+ if it is a member of the Autotuner's CURRENT declared config space
341
+ (kwargs + num_warps + num_stages equality against at.configs) — a
342
+ member is by definition a config a fresh bench could legally
343
+ select for the current kernel source and current policy. This
344
+ refuses resurrection of configs removed as buggy, refuses configs
345
+ whose constexpr names drifted with a source edit, and honors
346
+ NBX_DISABLE_AUTOTUNE's pinned single-config list (one member).
347
+ Non-members and non-literal keys are skipped: that shape simply
348
+ re-tunes, exactly the pre-E2 behavior."""
349
+ path = _artifact_path()
350
+ if path is None:
351
+ return 0
352
+ try:
353
+ with open(path) as f:
354
+ stored = json.load(f)
355
+ except (OSError, ValueError):
356
+ return 0
357
+ return _seed_entries(stored)
358
+
359
+
360
+ def _seed_entries(stored: Dict[str, Dict]) -> int:
361
+ """Insert `stored` configs into the Autotuner caches under the
362
+ membership gate documented on `seed()`. Returns the number seeded."""
363
+ seeded = 0
364
+ for qual, at in _autotuners():
365
+ prefix = f"{qual}::"
366
+ space = {(tuple(sorted(c.kwargs.items())), c.num_warps,
367
+ c.num_stages) for c in getattr(at, "configs", [])}
368
+ for k, d in stored.items():
369
+ if not k.startswith(prefix):
370
+ continue
371
+ member = (tuple(sorted(dict(d["kwargs"]).items())),
372
+ d["num_warps"], d["num_stages"])
373
+ if member not in space:
374
+ continue
375
+ try:
376
+ key = ast.literal_eval(k[len(prefix):])
377
+ except (ValueError, SyntaxError):
378
+ continue
379
+ if key not in at.cache:
380
+ try:
381
+ at.cache[key] = _config_from_dict(d)
382
+ seeded += 1
383
+ except Exception:
384
+ continue
385
+ return seeded
386
+
387
+
388
+ # ---------------------------------------------------------------------------
389
+ # The Autotuner's key, and the bench margin of a key this process measured
390
+ # ---------------------------------------------------------------------------
391
+ def key_of(at, args, kwargs) -> tuple:
392
+ """The Autotuner's cache key for a call, as `Autotuner.run` forms it."""
393
+ _args = {**dict(zip(at.arg_names, args)), **kwargs}
394
+ key = [_args[k] for k in at.keys if k in _args]
395
+ for _, arg in _args.items():
396
+ if hasattr(arg, "dtype"):
397
+ key.append(str(arg.dtype))
398
+ return tuple(key)
399
+
400
+
401
+ def _qual_of(at) -> Optional[str]:
402
+ for qual, obj in _autotuners():
403
+ if obj is at:
404
+ return qual
405
+ return None
406
+
407
+
408
+ _TIMINGS: Dict[Tuple[int, tuple], Dict] = {} # (id(tuner), key) -> best/second/margin of a bench this process ran
409
+
410
+
411
+ def note_timings(at, key: tuple, timings) -> None:
412
+ """Keep the bench of a key this process just measured: the best time, the
413
+ second-best, and the MARGIN between them. A cache that records only the
414
+ winner cannot say whether the winner was clear or a near-tie the timer
415
+ could flip on the next run — a gate comparing two sweeps needs exactly
416
+ that to tell a change of choice from noise. Written beside the config by
417
+ `capture()`."""
418
+ if not timings:
419
+ return
420
+ try:
421
+ times = sorted(float(t) for t in timings.values() if t is not None and float(t) == float(t))
422
+ except (TypeError, ValueError):
423
+ return
424
+ if not times:
425
+ return
426
+ best = times[0]
427
+ second = times[1] if len(times) > 1 else None
428
+ _TIMINGS[(id(at), key)] = {
429
+ "best_ms": best, "second_ms": second, "candidates": len(times),
430
+ "margin": (second / best - 1.0) if (second is not None and best > 0) else None}