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,266 @@
1
+ # The library's half of apple/apple_m2_pro.yml: the keys nbxtensor reads (docs/nbxtensor/device_keys.txt),
2
+ # moved with their sources by tools/split_vendor_files.py. NeuroBrix's half stays in
3
+ # src/neurobrix/config/vendors/apple/apple_m2_pro.yml; the engine reads the two merged.
4
+
5
+ architecture: apple_silicon
6
+ # provenance: measured 2026-09-10 — the backend builds this string
7
+ # from the device name (`driver.py`), verified on Apple M4 Pro.
8
+ compute_capability: "apple-m2-pro"
9
+ # What the Triton target actually reports, which is NOT the family above.
10
+ # MEASURED 2026-09-05 on an M4 Pro: the Metal backend builds its arch string
11
+ # from the Metal DEVICE NAME, lowercased and hyphenated
12
+ # (`kernels/metal_device.py::MetalRuntime.arch_name`), so it says
13
+ # "apple-m4-pro" where this file says "apple9". Matching on the family alone,
14
+ # the smem budget below resolved to NOTHING on a real Mac and the 32 KB
15
+ # filter silently did not apply — all 17 Volta tiles were offered, including
16
+ # the 72 KB one that cannot run here.
17
+ #
18
+ # These are prefixes, matched against the reported target. "apple-m" covers
19
+ # the whole M-series, which is exactly the scope this file claims in its
20
+ # header (M1..M4, GPU family 7/8/9). A chip outside it matches nothing and
21
+ # gets no filtering, which is the same zero-fallback answer as before.
22
+ compute_capability_matches:
23
+ # This file claims ONE variant. The family profile keeps the
24
+ # broad "apple-m" prefix and serves anything unknown.
25
+ - "apple-m2-pro"
26
+
27
+ block_sizes:
28
+ default: 1024
29
+
30
+ bmm:
31
+ block_m: 32
32
+ block_n: 32
33
+ block_k: 32
34
+ num_warps: 4
35
+
36
+ conv2d:
37
+ block_h: 16
38
+ block_w: 16
39
+ num_warps: 4
40
+
41
+ reduction: 1024
42
+
43
+ # Row-wise reductions over a dim (min / max / prod / all / any) and argmin.
44
+ #
45
+ # MEASURED 2026-09-07: `min` and `max` over a dim return a VALUE AND ITS
46
+ # INDEX, so their reduce carries a pair, and the Metal lowering stages such
47
+ # a reduce one element per thread — the tile must fit num_warps * 32. The
48
+ # adopted 8 x 1024 is 8192 against a 128-thread group.
49
+ #
50
+ # 8 x 128 with 32 warps is 1024 elements over 1024 threads, and is correct
51
+ # for max, min and argmin at N = 511, 1024, 1025 and 3077 against numpy in
52
+ # float64 (the row group is what matters here: at 8 rows the lowering used
53
+ # to collapse every row onto the first, which is fixed upstream and is why
54
+ # this entry can carry more than one row at all).
55
+ reduction_2d:
56
+ block_m: 8
57
+ block_n: 128
58
+ num_warps: 32
59
+
60
+ argmin:
61
+ block_m: 8
62
+ tile_n: 128
63
+ num_warps: 32
64
+
65
+ # Row-wise argmax — greedy sampling reads the argmax of a 32000-wide
66
+ # vocabulary row every token.
67
+ #
68
+ # MEASURED 2026-09-07: argmax is a MULTI-VALUE reduce (a value and its
69
+ # index travel together), and the Metal lowering has no multipass form for
70
+ # a tuple — it says so and refuses — so the tile must fit the threadgroup,
71
+ # which is num_warps * 32. The 4096 cap every architecture used refused
72
+ # against the default 4 warps (128 threads). 1024 over 32 warps is the
73
+ # largest that fits; the index it returns is exact at every tile measured
74
+ # (128/4, 256/8, 512/16, 1024/32 all give the same index on a 32000-wide
75
+ # row), so this is throughput, not correctness.
76
+ argmax:
77
+ tile_n: 1024
78
+ num_warps: 32
79
+
80
+ # M=1 dequant-GEMV occupancy rule — see nvidia/volta.yml for the rationale
81
+ # (the wrapper halves BLOCK_N until the grid reaches min_programs).
82
+ # 2 waves x 40 GPU cores (M3/M4 Max class); validation_status: analytical.
83
+ # Key absent or 0 -> no adaptation.
84
+ dequant_gemv:
85
+ min_programs: 80
86
+
87
+ # sdpa_thresholds RETIRED (roadmap C3, 2026-10-22): the tl.dot flash's tile is certified per key by autotune
88
+ # certify (`launch_keys.flash_tiles_certified`), not read from a hand table.
89
+
90
+ memory:
91
+ # 32 KB of threadgroup memory per threadgroup (Apple GPU family 7+).
92
+ # The single most binding difference from every NVIDIA profile here.
93
+ # MEASURED 2026-09-05 on an M4 Pro: `MTLDevice.maxThreadgroupMemoryLength()`
94
+ # returns exactly 32768, so the documented figure holds on real hardware.
95
+ max_shared_memory_per_block: 32768
96
+ max_registers_per_block: 32768
97
+ # Apple GPUs schedule in 32-wide SIMD groups. Named `warp_size` because one
98
+ # hardware concept has ONE key across every profile — AMD's 64-wide
99
+ # wavefronts are declared under the same name.
100
+ warp_size: 32
101
+
102
+ # SDPA deterministic-routing budget. 0 = NOT APPLICABLE: the
103
+ # non-determinism this routes around is a Volta-SIMT MMA issue and does
104
+ # not transfer to Apple's simdgroup path. Left 0 until shown otherwise on
105
+ # real hardware.
106
+ sdpa_math_max_scores_bytes: 0
107
+ sdpa_math_min_chunk_rows: 128
108
+ # Allocator pool: fraction of device memory the free-list may keep parked.
109
+ # Apple Silicon is UNIFIED memory — the "device" budget is system RAM
110
+ # shared with everything else on the machine, so parking is far less
111
+ # polite here than on a discrete card. Halved accordingly.
112
+ alloc_pool_parked_cap_fraction: 0.25
113
+ precision:
114
+ # Whether each engine branch's OWN kernels compute float64 / complex128 elements, read with
115
+ # supports_fp64 (both must hold) -- a CAPABILITY: `compiled` is ATen's, which carries both wherever
116
+ # the device does; `triton` is the NeuroBrix Triton kernels' (cuda/hip lower double; Metal's
117
+ # lowering narrows it in silence, measured 2026-09-28 on the Sana 4K positional embedding). Read
118
+ # by the DtypeEngine (`config.device_supports_fp64`) and by the Triton wrappers' binding
119
+ # (`triton.dtype.triton_has_fp64` -> `nbx_tensor.set_backend_has_fp64`).
120
+ kernels_carry_fp64:
121
+ compiled: true
122
+ triton: false
123
+ # Attention operands that disagree in dtype (an fp16 GEMM stored fp32 feeding V) all take the
124
+ # narrowest of the three, the compiled engine's alignment: the half attention stays on the matrix
125
+ # unit (V100, 2026-10-08: 1.16 s/op aligned narrowest vs 22.0 s/op on the scalar flash aligned widest).
126
+ attention_operands: narrowest
127
+ # Software-pipelining budget — see nvidia/volta.yml for the schema.
128
+ #
129
+ # validation_status: conservative_pending_first_light. cp.async is a PTX
130
+ # instruction with no Apple equivalent, and the Metal backend stages through
131
+ # threadgroup memory by a different mechanism. Neither Volta's measured
132
+ # fault nor Ampere's clearance transfers, so the budget stays at 2 until an
133
+ # Apple GPU measures it: clamping can only cost throughput.
134
+ pipelining:
135
+ max_num_stages: 2
136
+
137
+ # How work reaches the device.
138
+ #
139
+ # A launch used to commit its command buffer and block on
140
+ # `waitUntilCompleted()` before returning, so every kernel cost a host round
141
+ # trip. Measured 2026-09-07 on TinyLlama decode: 598 us per launch across
142
+ # 10,695 launches, 1,337 of them in a single decode step — 799 ms of an
143
+ # 857 ms step. Metal executes one queue's buffers in commit order, so the
144
+ # wait was never what made the engine correct; it only made every host read
145
+ # of device memory safe by accident. The waits moved to the host accesses
146
+ # themselves and the launch path commits without blocking.
147
+ #
148
+ # `max_command_buffers_in_flight` is what a queue may hold committed and not
149
+ # yet awaited. Metal's own ceiling is 64 per queue and it BLOCKS inside
150
+ # `commandBuffer()` at that point — a stall with nobody's name on it. 32
151
+ # keeps the backpressure ours and visible, at half the ceiling; it is a
152
+ # starting point measured on an M4 Pro, not a hardware constant.
153
+ dispatch:
154
+ max_command_buffers_in_flight: 32
155
+ # Dispatches one command buffer carries before the stream commits it. A
156
+ # NeuroBrix stream is one command buffer with one serialised compute
157
+ # encoder; this bounds how long the host waits for a result and how much
158
+ # work a single failure takes down with it. It is not a correctness knob —
159
+ # the encoder's own barriers keep the order at any value. A decode step of
160
+ # TinyLlama encodes ~1,350 dispatches, so 512 puts it in three buffers
161
+ # where one per launch put it in 1,505.
162
+ max_dispatches_per_command_buffer: 512
163
+
164
+ # Autotune correctness screen. The engine runs every candidate config once and
165
+ # compares its result against a reference config's before timing any of them,
166
+ # because an autotuner that ranks on speed alone selects whichever WRONG
167
+ # kernel is fastest — a kernel that writes half its output does half the
168
+ # stores and wins. Measured on Apple 2026-09-07: fp16 mm [64,32]@[32,64] chose
169
+ # a config that left half the columns zero.
170
+ #
171
+ # These are the tolerances that separate "a different but valid summation
172
+ # order" from "a different answer". Two configs with different block sizes
173
+ # reduce in a different order, so their results differ in the last bits; a
174
+ # config that drops output differs by the scale of the output itself. The
175
+ # gap between those two is several orders of magnitude, and these sit in it.
176
+ #
177
+ # Relative to the reference's own magnitude: max|a - b| / max|b|. Integer
178
+ # and boolean results are compared BIT-IDENTICALLY and ignore this table —
179
+ # there is no valid reordering that changes an integer.
180
+ autotune_screen_rtol:
181
+ float16: 2.0e-2
182
+ bfloat16: 4.0e-2
183
+ float32: 1.0e-4
184
+ float64: 1.0e-8
185
+ int64: 0.0 # an integer result (the MoE alignment's routing tables) is met exactly
186
+
187
+ # Above this many bytes of buffer, the autotune correctness screen does not
188
+ # run. Screening copies every argument out and back once per candidate, so a
189
+ # very large tensor turns a tuning step into hundreds of megabytes of traffic
190
+ # — and on unified memory that pressure is the machine's, not a card's.
191
+ # MEASURED 2026-09-07: unbounded screening of a 151936-wide softmax perturbed
192
+ # six unrelated tests in the same process. A skipped screen is announced, not
193
+ # silent.
194
+ autotune_screen_max_bytes: 33554432
195
+
196
+ autotune:
197
+ buckets:
198
+ # elements — an element-wise launch's element count (`kernels/ops/elementwise_launch.py`), NEVER the `default`
199
+ # dimension ladder (`autotune_bucket.ladder_for` refuses it). NOT measured on this architecture (no such device on
200
+ # either machine): the four classes measured on V100-SXM2 2026-10-23 (nvidia/volta.yml, launch-bound under 65 536
201
+ # elements, bandwidth classes above) and confirmed on apple_m4_pro 2026-10-24 — the measurement on this
202
+ # architecture is owed (docs/reference/owed-proofs.md, "element-wise ladder per architecture").
203
+ elements:
204
+ - {up_to: 65536, step: 65536}
205
+ - {up_to: 1048576, step: 1048576}
206
+ - {up_to: 16777216, step: 16777216}
207
+ - {up_to: null, step: 68719476736}
208
+ default:
209
+ - {up_to: 64, step: 1}
210
+ - {up_to: 256, step: 16}
211
+ - {up_to: 1024, step: 32}
212
+ - {up_to: 8192, step: 128}
213
+ # Above 8 192 a QUARTER-OCTAVE, not a 512-step. The 512 tail was decided by a sweep
214
+ # that ran to about 4 096 and never met a request-scale dimension: a vocoder's batched
215
+ # GEMM keys on the WAVEFORM and reaches 1 963 520, which a 512-step cuts into some four
216
+ # thousand buckets — 1 400 of one model's 1 522 distinct M values sat above 8 192.
217
+ # Measured 2026-09-22 on both classes, two shapes (the vocoder's B=1,N=1,K=9 and a
218
+ # GEMM at N=K=512), each octave bucket carrying three or four measured values with its
219
+ # TOP as representative: 0.0 % median loss, 0.2 % max at ONE octave bucket. The rows
220
+ # below are finer than the octave that was measured, so they cost no more than it did.
221
+ # The first three octaves above the knee keep a NARROW step. Measured directly on the
222
+ # rows as shipped (the upscalers' N=360, K=180; three sizes inside each bucket with
223
+ # its TOP among them, 2026-09-22): a quarter-octave there cost 0.0 % median but
224
+ # **5.2 %** at the 10 240 bucket and 1.2 % at 16 384 — the loss lives just above the
225
+ # knee, where a quarter of an OCTAVE is still a quarter of the VALUE. Beyond 65 536
226
+ # the same sweep measured 0.0 % median and at most 0.6 %, so the quarter-octave stands.
227
+ # The convolution-width ladder below keeps its quarter-octave: its own sweep measured
228
+ # 0.0 % median AND max there, on both classes and two channel counts.
229
+ - {up_to: 16384, step: 512}
230
+ - {up_to: 32768, step: 1024}
231
+ - {up_to: 65536, step: 2048}
232
+ - {up_to: 131072, step: 16384}
233
+ - {up_to: 262144, step: 32768}
234
+ - {up_to: 524288, step: 65536}
235
+ - {up_to: 1048576, step: 131072}
236
+ - {up_to: 2097152, step: 262144}
237
+ - {up_to: null, step: 524288}
238
+ # The width of a ONE-ROW convolution (a 1-D convolution over a sequence: a vocoder's
239
+ # samples, a mel spectrogram's frames) is the request's length, unbounded by any
240
+ # resolution — chatterbox's vocoder reaches 444 721 samples for 18 s of speech. Above the
241
+ # GEMM ladder it steps by a quarter-octave (≤ 25 % of the value), so a census meets every
242
+ # class of a speech length in ~100 runs instead of ~1 000; the loss of that step is owed a
243
+ # measurement on both classes (bucket_loss.py) before the ladder is called final.
244
+ W:
245
+ - {up_to: 64, step: 1}
246
+ - {up_to: 256, step: 16}
247
+ - {up_to: 1024, step: 32}
248
+ - {up_to: 8192, step: 128}
249
+ - {up_to: 16384, step: 2048}
250
+ - {up_to: 32768, step: 4096}
251
+ - {up_to: 65536, step: 8192}
252
+ - {up_to: 131072, step: 16384}
253
+ - {up_to: 262144, step: 32768}
254
+ - {up_to: 524288, step: 65536}
255
+ - {up_to: null, step: 131072}
256
+
257
+ # The flash kernel at a power-of-two head dim >= 128 is NOT measured on this architecture: the zero-pad
258
+ # detour stays (launch_keys.flash_headdim_detour). Declared, never left absent: true only where the
259
+ # correctness oracle was run with the detour off on this arch and stack (Volta, Triton 3.8, 2026-10-03).
260
+ flash:
261
+ pow2_head_dim_correct: false
262
+
263
+ # Convolution weight storage order (launch_keys.conv_weight_layout): KCRS, the container's own,
264
+ # until this arch measures KRSC (the M4 Pro measured +46-63 % on its 3x3 keys, 2026-10-07).
265
+ conv:
266
+ weight_layout: KCRS
@@ -0,0 +1,266 @@
1
+ # The library's half of apple/apple_m2_ultra.yml: the keys nbxtensor reads (docs/nbxtensor/device_keys.txt),
2
+ # moved with their sources by tools/split_vendor_files.py. NeuroBrix's half stays in
3
+ # src/neurobrix/config/vendors/apple/apple_m2_ultra.yml; the engine reads the two merged.
4
+
5
+ architecture: apple_silicon
6
+ # provenance: measured 2026-09-10 — the backend builds this string
7
+ # from the device name (`driver.py`), verified on Apple M4 Pro.
8
+ compute_capability: "apple-m2-ultra"
9
+ # What the Triton target actually reports, which is NOT the family above.
10
+ # MEASURED 2026-09-05 on an M4 Pro: the Metal backend builds its arch string
11
+ # from the Metal DEVICE NAME, lowercased and hyphenated
12
+ # (`kernels/metal_device.py::MetalRuntime.arch_name`), so it says
13
+ # "apple-m4-pro" where this file says "apple9". Matching on the family alone,
14
+ # the smem budget below resolved to NOTHING on a real Mac and the 32 KB
15
+ # filter silently did not apply — all 17 Volta tiles were offered, including
16
+ # the 72 KB one that cannot run here.
17
+ #
18
+ # These are prefixes, matched against the reported target. "apple-m" covers
19
+ # the whole M-series, which is exactly the scope this file claims in its
20
+ # header (M1..M4, GPU family 7/8/9). A chip outside it matches nothing and
21
+ # gets no filtering, which is the same zero-fallback answer as before.
22
+ compute_capability_matches:
23
+ # This file claims ONE variant. The family profile keeps the
24
+ # broad "apple-m" prefix and serves anything unknown.
25
+ - "apple-m2-ultra"
26
+
27
+ block_sizes:
28
+ default: 1024
29
+
30
+ bmm:
31
+ block_m: 32
32
+ block_n: 32
33
+ block_k: 32
34
+ num_warps: 4
35
+
36
+ conv2d:
37
+ block_h: 16
38
+ block_w: 16
39
+ num_warps: 4
40
+
41
+ reduction: 1024
42
+
43
+ # Row-wise reductions over a dim (min / max / prod / all / any) and argmin.
44
+ #
45
+ # MEASURED 2026-09-07: `min` and `max` over a dim return a VALUE AND ITS
46
+ # INDEX, so their reduce carries a pair, and the Metal lowering stages such
47
+ # a reduce one element per thread — the tile must fit num_warps * 32. The
48
+ # adopted 8 x 1024 is 8192 against a 128-thread group.
49
+ #
50
+ # 8 x 128 with 32 warps is 1024 elements over 1024 threads, and is correct
51
+ # for max, min and argmin at N = 511, 1024, 1025 and 3077 against numpy in
52
+ # float64 (the row group is what matters here: at 8 rows the lowering used
53
+ # to collapse every row onto the first, which is fixed upstream and is why
54
+ # this entry can carry more than one row at all).
55
+ reduction_2d:
56
+ block_m: 8
57
+ block_n: 128
58
+ num_warps: 32
59
+
60
+ argmin:
61
+ block_m: 8
62
+ tile_n: 128
63
+ num_warps: 32
64
+
65
+ # Row-wise argmax — greedy sampling reads the argmax of a 32000-wide
66
+ # vocabulary row every token.
67
+ #
68
+ # MEASURED 2026-09-07: argmax is a MULTI-VALUE reduce (a value and its
69
+ # index travel together), and the Metal lowering has no multipass form for
70
+ # a tuple — it says so and refuses — so the tile must fit the threadgroup,
71
+ # which is num_warps * 32. The 4096 cap every architecture used refused
72
+ # against the default 4 warps (128 threads). 1024 over 32 warps is the
73
+ # largest that fits; the index it returns is exact at every tile measured
74
+ # (128/4, 256/8, 512/16, 1024/32 all give the same index on a 32000-wide
75
+ # row), so this is throughput, not correctness.
76
+ argmax:
77
+ tile_n: 1024
78
+ num_warps: 32
79
+
80
+ # M=1 dequant-GEMV occupancy rule — see nvidia/volta.yml for the rationale
81
+ # (the wrapper halves BLOCK_N until the grid reaches min_programs).
82
+ # 2 waves x 40 GPU cores (M3/M4 Max class); validation_status: analytical.
83
+ # Key absent or 0 -> no adaptation.
84
+ dequant_gemv:
85
+ min_programs: 80
86
+
87
+ # sdpa_thresholds RETIRED (roadmap C3, 2026-10-22): the tl.dot flash's tile is certified per key by autotune
88
+ # certify (`launch_keys.flash_tiles_certified`), not read from a hand table.
89
+
90
+ memory:
91
+ # 32 KB of threadgroup memory per threadgroup (Apple GPU family 7+).
92
+ # The single most binding difference from every NVIDIA profile here.
93
+ # MEASURED 2026-09-05 on an M4 Pro: `MTLDevice.maxThreadgroupMemoryLength()`
94
+ # returns exactly 32768, so the documented figure holds on real hardware.
95
+ max_shared_memory_per_block: 32768
96
+ max_registers_per_block: 32768
97
+ # Apple GPUs schedule in 32-wide SIMD groups. Named `warp_size` because one
98
+ # hardware concept has ONE key across every profile — AMD's 64-wide
99
+ # wavefronts are declared under the same name.
100
+ warp_size: 32
101
+
102
+ # SDPA deterministic-routing budget. 0 = NOT APPLICABLE: the
103
+ # non-determinism this routes around is a Volta-SIMT MMA issue and does
104
+ # not transfer to Apple's simdgroup path. Left 0 until shown otherwise on
105
+ # real hardware.
106
+ sdpa_math_max_scores_bytes: 0
107
+ sdpa_math_min_chunk_rows: 128
108
+ # Allocator pool: fraction of device memory the free-list may keep parked.
109
+ # Apple Silicon is UNIFIED memory — the "device" budget is system RAM
110
+ # shared with everything else on the machine, so parking is far less
111
+ # polite here than on a discrete card. Halved accordingly.
112
+ alloc_pool_parked_cap_fraction: 0.25
113
+ precision:
114
+ # Whether each engine branch's OWN kernels compute float64 / complex128 elements, read with
115
+ # supports_fp64 (both must hold) -- a CAPABILITY: `compiled` is ATen's, which carries both wherever
116
+ # the device does; `triton` is the NeuroBrix Triton kernels' (cuda/hip lower double; Metal's
117
+ # lowering narrows it in silence, measured 2026-09-28 on the Sana 4K positional embedding). Read
118
+ # by the DtypeEngine (`config.device_supports_fp64`) and by the Triton wrappers' binding
119
+ # (`triton.dtype.triton_has_fp64` -> `nbx_tensor.set_backend_has_fp64`).
120
+ kernels_carry_fp64:
121
+ compiled: true
122
+ triton: false
123
+ # Attention operands that disagree in dtype (an fp16 GEMM stored fp32 feeding V) all take the
124
+ # narrowest of the three, the compiled engine's alignment: the half attention stays on the matrix
125
+ # unit (V100, 2026-10-08: 1.16 s/op aligned narrowest vs 22.0 s/op on the scalar flash aligned widest).
126
+ attention_operands: narrowest
127
+ # Software-pipelining budget — see nvidia/volta.yml for the schema.
128
+ #
129
+ # validation_status: conservative_pending_first_light. cp.async is a PTX
130
+ # instruction with no Apple equivalent, and the Metal backend stages through
131
+ # threadgroup memory by a different mechanism. Neither Volta's measured
132
+ # fault nor Ampere's clearance transfers, so the budget stays at 2 until an
133
+ # Apple GPU measures it: clamping can only cost throughput.
134
+ pipelining:
135
+ max_num_stages: 2
136
+
137
+ # How work reaches the device.
138
+ #
139
+ # A launch used to commit its command buffer and block on
140
+ # `waitUntilCompleted()` before returning, so every kernel cost a host round
141
+ # trip. Measured 2026-09-07 on TinyLlama decode: 598 us per launch across
142
+ # 10,695 launches, 1,337 of them in a single decode step — 799 ms of an
143
+ # 857 ms step. Metal executes one queue's buffers in commit order, so the
144
+ # wait was never what made the engine correct; it only made every host read
145
+ # of device memory safe by accident. The waits moved to the host accesses
146
+ # themselves and the launch path commits without blocking.
147
+ #
148
+ # `max_command_buffers_in_flight` is what a queue may hold committed and not
149
+ # yet awaited. Metal's own ceiling is 64 per queue and it BLOCKS inside
150
+ # `commandBuffer()` at that point — a stall with nobody's name on it. 32
151
+ # keeps the backpressure ours and visible, at half the ceiling; it is a
152
+ # starting point measured on an M4 Pro, not a hardware constant.
153
+ dispatch:
154
+ max_command_buffers_in_flight: 32
155
+ # Dispatches one command buffer carries before the stream commits it. A
156
+ # NeuroBrix stream is one command buffer with one serialised compute
157
+ # encoder; this bounds how long the host waits for a result and how much
158
+ # work a single failure takes down with it. It is not a correctness knob —
159
+ # the encoder's own barriers keep the order at any value. A decode step of
160
+ # TinyLlama encodes ~1,350 dispatches, so 512 puts it in three buffers
161
+ # where one per launch put it in 1,505.
162
+ max_dispatches_per_command_buffer: 512
163
+
164
+ # Autotune correctness screen. The engine runs every candidate config once and
165
+ # compares its result against a reference config's before timing any of them,
166
+ # because an autotuner that ranks on speed alone selects whichever WRONG
167
+ # kernel is fastest — a kernel that writes half its output does half the
168
+ # stores and wins. Measured on Apple 2026-09-07: fp16 mm [64,32]@[32,64] chose
169
+ # a config that left half the columns zero.
170
+ #
171
+ # These are the tolerances that separate "a different but valid summation
172
+ # order" from "a different answer". Two configs with different block sizes
173
+ # reduce in a different order, so their results differ in the last bits; a
174
+ # config that drops output differs by the scale of the output itself. The
175
+ # gap between those two is several orders of magnitude, and these sit in it.
176
+ #
177
+ # Relative to the reference's own magnitude: max|a - b| / max|b|. Integer
178
+ # and boolean results are compared BIT-IDENTICALLY and ignore this table —
179
+ # there is no valid reordering that changes an integer.
180
+ autotune_screen_rtol:
181
+ float16: 2.0e-2
182
+ bfloat16: 4.0e-2
183
+ float32: 1.0e-4
184
+ float64: 1.0e-8
185
+ int64: 0.0 # an integer result (the MoE alignment's routing tables) is met exactly
186
+
187
+ # Above this many bytes of buffer, the autotune correctness screen does not
188
+ # run. Screening copies every argument out and back once per candidate, so a
189
+ # very large tensor turns a tuning step into hundreds of megabytes of traffic
190
+ # — and on unified memory that pressure is the machine's, not a card's.
191
+ # MEASURED 2026-09-07: unbounded screening of a 151936-wide softmax perturbed
192
+ # six unrelated tests in the same process. A skipped screen is announced, not
193
+ # silent.
194
+ autotune_screen_max_bytes: 33554432
195
+
196
+ autotune:
197
+ buckets:
198
+ # elements — an element-wise launch's element count (`kernels/ops/elementwise_launch.py`), NEVER the `default`
199
+ # dimension ladder (`autotune_bucket.ladder_for` refuses it). NOT measured on this architecture (no such device on
200
+ # either machine): the four classes measured on V100-SXM2 2026-10-23 (nvidia/volta.yml, launch-bound under 65 536
201
+ # elements, bandwidth classes above) and confirmed on apple_m4_pro 2026-10-24 — the measurement on this
202
+ # architecture is owed (docs/reference/owed-proofs.md, "element-wise ladder per architecture").
203
+ elements:
204
+ - {up_to: 65536, step: 65536}
205
+ - {up_to: 1048576, step: 1048576}
206
+ - {up_to: 16777216, step: 16777216}
207
+ - {up_to: null, step: 68719476736}
208
+ default:
209
+ - {up_to: 64, step: 1}
210
+ - {up_to: 256, step: 16}
211
+ - {up_to: 1024, step: 32}
212
+ - {up_to: 8192, step: 128}
213
+ # Above 8 192 a QUARTER-OCTAVE, not a 512-step. The 512 tail was decided by a sweep
214
+ # that ran to about 4 096 and never met a request-scale dimension: a vocoder's batched
215
+ # GEMM keys on the WAVEFORM and reaches 1 963 520, which a 512-step cuts into some four
216
+ # thousand buckets — 1 400 of one model's 1 522 distinct M values sat above 8 192.
217
+ # Measured 2026-09-22 on both classes, two shapes (the vocoder's B=1,N=1,K=9 and a
218
+ # GEMM at N=K=512), each octave bucket carrying three or four measured values with its
219
+ # TOP as representative: 0.0 % median loss, 0.2 % max at ONE octave bucket. The rows
220
+ # below are finer than the octave that was measured, so they cost no more than it did.
221
+ # The first three octaves above the knee keep a NARROW step. Measured directly on the
222
+ # rows as shipped (the upscalers' N=360, K=180; three sizes inside each bucket with
223
+ # its TOP among them, 2026-09-22): a quarter-octave there cost 0.0 % median but
224
+ # **5.2 %** at the 10 240 bucket and 1.2 % at 16 384 — the loss lives just above the
225
+ # knee, where a quarter of an OCTAVE is still a quarter of the VALUE. Beyond 65 536
226
+ # the same sweep measured 0.0 % median and at most 0.6 %, so the quarter-octave stands.
227
+ # The convolution-width ladder below keeps its quarter-octave: its own sweep measured
228
+ # 0.0 % median AND max there, on both classes and two channel counts.
229
+ - {up_to: 16384, step: 512}
230
+ - {up_to: 32768, step: 1024}
231
+ - {up_to: 65536, step: 2048}
232
+ - {up_to: 131072, step: 16384}
233
+ - {up_to: 262144, step: 32768}
234
+ - {up_to: 524288, step: 65536}
235
+ - {up_to: 1048576, step: 131072}
236
+ - {up_to: 2097152, step: 262144}
237
+ - {up_to: null, step: 524288}
238
+ # The width of a ONE-ROW convolution (a 1-D convolution over a sequence: a vocoder's
239
+ # samples, a mel spectrogram's frames) is the request's length, unbounded by any
240
+ # resolution — chatterbox's vocoder reaches 444 721 samples for 18 s of speech. Above the
241
+ # GEMM ladder it steps by a quarter-octave (≤ 25 % of the value), so a census meets every
242
+ # class of a speech length in ~100 runs instead of ~1 000; the loss of that step is owed a
243
+ # measurement on both classes (bucket_loss.py) before the ladder is called final.
244
+ W:
245
+ - {up_to: 64, step: 1}
246
+ - {up_to: 256, step: 16}
247
+ - {up_to: 1024, step: 32}
248
+ - {up_to: 8192, step: 128}
249
+ - {up_to: 16384, step: 2048}
250
+ - {up_to: 32768, step: 4096}
251
+ - {up_to: 65536, step: 8192}
252
+ - {up_to: 131072, step: 16384}
253
+ - {up_to: 262144, step: 32768}
254
+ - {up_to: 524288, step: 65536}
255
+ - {up_to: null, step: 131072}
256
+
257
+ # The flash kernel at a power-of-two head dim >= 128 is NOT measured on this architecture: the zero-pad
258
+ # detour stays (launch_keys.flash_headdim_detour). Declared, never left absent: true only where the
259
+ # correctness oracle was run with the detour off on this arch and stack (Volta, Triton 3.8, 2026-10-03).
260
+ flash:
261
+ pow2_head_dim_correct: false
262
+
263
+ # Convolution weight storage order (launch_keys.conv_weight_layout): KCRS, the container's own,
264
+ # until this arch measures KRSC (the M4 Pro measured +46-63 % on its 3x3 keys, 2026-10-07).
265
+ conv:
266
+ weight_layout: KCRS