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