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 @@
1
+ """Dtypes: the one table of widths (`dtypes.yml`) and its reader (`itemsize`)."""
@@ -0,0 +1,107 @@
1
+ """Host conversions between bfloat16, float16 and float32 — the one module that does them.
2
+
3
+ numpy has no bfloat16. A bfloat16 array on the host is carried as its 16-bit patterns (`uint16`), or as the 2-byte
4
+ void array a bfloat16 tensor's host copy comes back as (`<V2`, the array interface's typestr for a type numpy does
5
+ not know). Every conversion between that carrier and real numbers lives here, once: rounding float32 to bfloat16,
6
+ widening bfloat16 to float32, and narrowing bfloat16 to float16.
7
+
8
+ Pure functions of numpy arrays: no device, no global state, no thread pool of their own (R-NBXT 11). A caller that
9
+ splits a large array across workers calls them on each span with `out=`.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from typing import Optional
14
+
15
+ import numpy as np
16
+
17
+ __all__ = ["f32_to_bf16_bits", "bf16_bits_to_f32", "bf16_bits_to_f16", "bf16_bits", "bf16_abs_max", "saturate_scalar"]
18
+
19
+ # The quiet NaN every NaN becomes, sign dropped: PyTorch's `round_to_nearest_even`
20
+ # (torch/headeronly/util/BFloat16.h: `if (std::isnan(src)) return UINT16_C(0x7FC0);`).
21
+ _QUIET_NAN = np.uint16(0x7FC0)
22
+ # The elements one step of the rounding works on: its temporaries are one step's, never the operand's size (a 5.1 GB
23
+ # operand took four full-size uint32 temporaries and the Mac's certifier to its 14 GB guard, 2026-09-28).
24
+ _STEP = 1 << 18
25
+
26
+
27
+ def bf16_bits(a: np.ndarray) -> np.ndarray:
28
+ """The `uint16` patterns of a bfloat16 host carrier: the array itself when it already is `uint16`, a view when
29
+ it is the 2-byte void carrier. Anything else is refused by name."""
30
+ a = np.asarray(a)
31
+ if a.dtype == np.uint16:
32
+ return a
33
+ if a.dtype.kind == "V" and a.dtype.itemsize == 2:
34
+ return np.ascontiguousarray(a).view(np.uint16)
35
+ raise TypeError(f"a bfloat16 host carrier is uint16 bits or a 2-byte void array, not {a.dtype}")
36
+
37
+
38
+ def f32_to_bf16_bits(a: np.ndarray, out: Optional[np.ndarray] = None) -> np.ndarray:
39
+ """float32 values narrowed to bfloat16, returned as their `uint16` patterns: round to nearest, ties to even —
40
+ the rounding `torch.Tensor.to(torch.bfloat16)` applies and the device cast applies. `+0x7FFF` alone rounds an
41
+ exact half down; `((u >> 16) & 1)` adds the one that makes it ties-to-even. Values past the bfloat16 maximum
42
+ become infinities; every NaN becomes the quiet NaN 0x7FC0. Streamed in fixed steps: the peak is the output plus
43
+ one step."""
44
+ a = np.ascontiguousarray(a, dtype=np.float32)
45
+ flat = a.reshape(-1)
46
+ u = flat.view(np.uint32)
47
+ bits = np.empty(flat.shape, dtype=np.uint16) if out is None else out.reshape(-1)
48
+ for i in range(0, flat.size, _STEP):
49
+ uc = u[i:i + _STEP]
50
+ t = np.right_shift(uc, np.uint32(16))
51
+ np.bitwise_and(t, np.uint32(1), out=t)
52
+ np.add(t, np.uint32(0x7FFF), out=t)
53
+ np.add(t, uc, out=t)
54
+ np.right_shift(t, np.uint32(16), out=t)
55
+ bits[i:i + _STEP] = t
56
+ nan = np.isnan(flat[i:i + _STEP])
57
+ if nan.any():
58
+ bits[i:i + _STEP][nan] = _QUIET_NAN
59
+ return bits.reshape(a.shape) if out is None else out
60
+
61
+
62
+ def bf16_bits_to_f32(bits: np.ndarray) -> np.ndarray:
63
+ """The exact float32 values of bfloat16 patterns (`uint16`, or the 2-byte void carrier): a bfloat16 is the top
64
+ sixteen bits of a float32, so the widening loses nothing."""
65
+ b = np.ascontiguousarray(bf16_bits(bits))
66
+ return (b.astype(np.uint32) << np.uint32(16)).view(np.float32).reshape(b.shape)
67
+
68
+
69
+ def bf16_bits_to_f16(bits: np.ndarray, out: Optional[np.ndarray] = None) -> np.ndarray:
70
+ """bfloat16 patterns narrowed to float16, exactly as widening to float32 then numpy's float32 -> float16 cast
71
+ (round to nearest even; past the float16 range: infinity). `out`, when given, is a float16 array of the same
72
+ number of elements the result is written into (a caller splitting the work across workers passes spans)."""
73
+ src = np.ascontiguousarray(bf16_bits(bits)).reshape(-1)
74
+ dst = np.empty(src.shape, dtype=np.float16) if out is None else out.reshape(-1)
75
+ np.copyto(dst, (src.astype(np.uint32) << np.uint32(16)).view(np.float32), casting="unsafe")
76
+ return dst.reshape(np.shape(bits)) if out is None else out
77
+
78
+
79
+ def bf16_abs_max(bits: np.ndarray) -> float:
80
+ """The largest magnitude among bfloat16 patterns, NaN ignored (an infinity is infinite): what a caller compares to
81
+ a narrower dtype's `finite_max` to know whether the values fit it. Streamed in fixed steps."""
82
+ flat = np.ascontiguousarray(bf16_bits(bits)).reshape(-1)
83
+ top = 0.0
84
+ for i in range(0, flat.size, _STEP):
85
+ v = np.abs((flat[i:i + _STEP].astype(np.uint32) << np.uint32(16)).view(np.float32))
86
+ v = v[~np.isnan(v)]
87
+ if v.size:
88
+ top = max(top, float(v.max()))
89
+ return top
90
+
91
+
92
+ def saturate_scalar(value, dtype):
93
+ """A Python number clamped to the finite range of a half floating `dtype` (a name or an enum member; the range is
94
+ the dtype table's `max`), anything else unchanged: a bool, a non-number, a dtype wider than two bytes or not
95
+ floating, a NaN, an infinity the graph wrote on purpose. A mask sentinel written for one half format then holds in
96
+ the other (bfloat16's -3.39e38 becomes float16's -65504)."""
97
+ from nbxtensor.dtype.itemsize import canonical_name, finite_max, itemsize, kind
98
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
99
+ return value
100
+ name = canonical_name(dtype)
101
+ if kind(name) != "float" or itemsize(name) != 2:
102
+ return value
103
+ v = float(value)
104
+ if v != v or v in (float("inf"), float("-inf")):
105
+ return value
106
+ top = finite_max(name)
107
+ return max(-top, min(top, v))
@@ -0,0 +1,70 @@
1
+ # The dtype table: the width of every dtype the engine knows, one entry each.
2
+ #
3
+ # Read through ONE reader and nowhere else: `nbxtensor/dtype/itemsize.py` (both engine branches, Prism, NBXTensor, the
4
+ # wrappers, the launch keys, the certifier). A dtype absent from this file is refused BY NAME: no width is ever guessed
5
+ # (the silent `.get(dtype, 4)` sized a complex64 at half its bytes until 2026-09-22, Kokoro-82M's vocoder).
6
+ #
7
+ # Per entry:
8
+ # bits storage bits per element (bytes = bits / 8 for every dense dtype)
9
+ # typestr the array-interface type string of the element (`__cuda_array_interface__`, numpy's
10
+ # `__array_interface__`); bfloat16, which numpy cannot name, is the 2-byte void '<V2'
11
+ # numpy the numpy dtype name, when numpy has one
12
+ # aliases other spellings a caller may name it by (torch's `half`, `float`, `double`, `int`,
13
+ # `long`, `byte`)
14
+ # kind float, int, uint, bool or complex — the class the promotion rule reads
15
+ # (nbxtensor.dtype.promotion)
16
+ # max a float's largest finite value: (2 - 2^(1-p)) x 2^emax for an IEEE-style format of p
17
+ # significant bits (binary64, binary32, binary16; bfloat16 = binary32's exponent with
18
+ # 8 significant bits; TF32 = 11 significant bits), and the OCP OFP8 v1.0 table for the
19
+ # two float8 formats (E4M3: 448, no infinity; E5M2: 57344)
20
+ # safetensors the dtype code a safetensors header writes for it (spec:
21
+ # https://github.com/huggingface/safetensors — `Dtype`), when it has one
22
+ # autotune_keys the spellings a launch key / certified directory carries for it
23
+ # (`kernels/autotune_certified._DTYPES`)
24
+ # without_fp64 what a store with no float64 holds in its place (the device's or the engine's
25
+ # fp64 ceiling: MPS refuses a float64 tensor and the vendors' own code narrows to
26
+ # float32 there; the Triton kernels are fp32-max, `TritonSequence._parse_dtype`)
27
+ #
28
+ # A hi+lo split operand is TWO tensors of the low dtype: that is a representation, computed by
29
+ # the readers' `representation_bytes(dtype, numel, split=True)`, never an entry here.
30
+
31
+ dtypes:
32
+ # IEEE 754-2008 binary64.
33
+ float64: {bits: 64, kind: float, max: 1.7976931348623157e+308, safetensors: F64, autotune_keys: [fp64], without_fp64: float32, typestr: '<f8', numpy: float64, aliases: [double]}
34
+ # IEEE 754-2008 binary32.
35
+ float32: {bits: 32, kind: float, max: 3.4028234663852886e+38, safetensors: F32, autotune_keys: [fp32], typestr: '<f4', numpy: float32, aliases: [float]}
36
+ # NVIDIA TF32 (A100 architecture whitepaper): a tensor-core COMPUTE format of 19 significant
37
+ # bits, stored and moved in a full 32-bit fp32 word.
38
+ tfloat32: {bits: 32, kind: float, max: 3.4011621342146535e+38}
39
+ # Google Brain bfloat16: the top 16 bits of a binary32.
40
+ bfloat16: {bits: 16, kind: float, max: 3.3895313892515355e+38, safetensors: BF16, autotune_keys: [bf16], typestr: '<V2'}
41
+ # IEEE 754-2008 binary16.
42
+ float16: {bits: 16, kind: float, max: 65504.0, safetensors: F16, autotune_keys: [fp16], typestr: '<f2', numpy: float16, aliases: [half]}
43
+ # OCP 8-bit Floating Point Specification (OFP8) v1.0, E4M3 (finite, no inf) and E5M2;
44
+ # torch.float8_e4m3fn / torch.float8_e5m2. The fnuz variants appear nowhere in this code.
45
+ float8_e4m3fn: {bits: 8, kind: float, max: 448.0, safetensors: F8_E4M3}
46
+ float8_e5m2: {bits: 8, kind: float, max: 57344.0, safetensors: F8_E5M2}
47
+ # Two's-complement integers of the stated width (C99 <stdint.h>, numpy, torch).
48
+ int64: {bits: 64, kind: int, safetensors: I64, autotune_keys: [int64], typestr: '<i8', numpy: int64, aliases: [long]}
49
+ int32: {bits: 32, kind: int, safetensors: I32, autotune_keys: [int32], typestr: '<i4', numpy: int32, aliases: [int]}
50
+ int16: {bits: 16, kind: int, safetensors: I16, autotune_keys: [int16], typestr: '<i2', numpy: int16}
51
+ int8: {bits: 8, kind: int, safetensors: I8, autotune_keys: [int8], typestr: '<i1', numpy: int8}
52
+ # Unsigned integers of the stated width; uint16/32/64 are carried by launch keys (a bf16 bit
53
+ # carrier, sort and RNG kernels), not by any container.
54
+ uint64: {bits: 64, kind: uint, autotune_keys: [uint64]}
55
+ uint32: {bits: 32, kind: uint, autotune_keys: [uint32]}
56
+ uint16: {bits: 16, kind: uint, autotune_keys: [uint16]}
57
+ uint8: {bits: 8, kind: uint, safetensors: U8, autotune_keys: [uint8], typestr: '<u1', numpy: uint8, aliases: [byte]}
58
+ # One byte per element in torch and numpy (no bit packing); `int1` is Triton's spelling.
59
+ bool: {bits: 8, kind: bool, safetensors: BOOL, autotune_keys: [int1, bool], typestr: '|b1', numpy: bool}
60
+ # Two binary32 / two binary64, interleaved [real, imag] (numpy and torch layout).
61
+ complex64: {bits: 64, kind: complex, typestr: '<c8', numpy: complex64}
62
+ complex128: {bits: 128, kind: complex, without_fp64: complex64, typestr: '<c16', numpy: complex128}
63
+
64
+ # Packed weight encodings: bits per element is a fraction of a byte and the group scales live
65
+ # beside the packed values. Only the encodings this engine executes
66
+ # (`core/runtime/weight_encoding.SUPPORTED_WEIGHT_ENCODINGS`).
67
+ packed:
68
+ # Weight-only int4, groups of 128 along in_features, asymmetric: one float16 scale and one
69
+ # float16 minimum per group; 8 nibbles per int32 (core/runtime/weight_encoding.py).
70
+ int4-g128-asym: {bits: 4, group_size: 128, scale_dtype: float16, min_dtype: float16}
@@ -0,0 +1,166 @@
1
+ """The dtype table and its reader — the ONE place a dtype's width is written (`dtypes.yml`, beside this module).
2
+
3
+ Every width a dtype has is read from `dtypes.yml` through this module, and through no other reader: NeuroBrix's two
4
+ engine branches read it here.
5
+
6
+ Torch-free and numpy-free, at import and at execution.
7
+
8
+ A dtype is named as the code passes it today: a name ("float16"), a torch spelling ("torch.float16", as a traced
9
+ graph writes some — read as text, torch is never imported), or an enum member read by its NAME (NBXDtype is an IntEnum,
10
+ and `str()` of one is its bare number on Python 3.11+ — never parsed). A name the table does not carry is refused by
11
+ name, never given a width.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import copy
16
+ import functools
17
+ from pathlib import Path
18
+ from enum import Enum
19
+ from typing import Any, Dict, Tuple
20
+
21
+ _SOURCE = "nbxtensor/dtype/dtypes.yml"
22
+ _TABLE = Path(__file__).resolve().parent / "dtypes.yml"
23
+ _TORCH_PREFIX = "torch."
24
+ _KINDS = ("float", "int", "uint", "bool", "complex")
25
+
26
+
27
+ @functools.lru_cache(maxsize=1)
28
+ def _raw_table() -> Dict[str, Any]:
29
+ import yaml
30
+ if not _TABLE.is_file():
31
+ raise FileNotFoundError(f"ZERO FALLBACK: the dtype table is not found: {_TABLE}")
32
+ table = yaml.safe_load(_TABLE.read_text()) or {}
33
+ for section in ("dtypes", "packed"):
34
+ if not isinstance(table.get(section), dict):
35
+ raise KeyError(f"ZERO FALLBACK: {_TABLE} carries no {section!r} section")
36
+ return table
37
+
38
+
39
+ def dtype_table() -> Dict[str, Any]:
40
+ """The whole table as written (`dtypes`, `packed`, and every other section), a copy: the reader a client keeps of
41
+ its own is fed from here, never from a second file."""
42
+ return copy.deepcopy(_raw_table())
43
+
44
+
45
+ @functools.lru_cache(maxsize=1)
46
+ def _tables() -> Tuple[Dict[str, Dict[str, Any]], Dict[str, Dict[str, Any]], Dict[str, str], Dict[str, str]]:
47
+ table = _raw_table()
48
+ dense, packed = dict(table["dtypes"]), dict(table["packed"])
49
+ for name, entry in dense.items():
50
+ bits = entry.get("bits")
51
+ if not isinstance(bits, int) or bits <= 0 or bits % 8:
52
+ raise ValueError(f"ZERO FALLBACK: {_SOURCE} gives dtype {name!r} {bits!r} bits — a dense "
53
+ f"dtype is a whole number of bytes")
54
+ if entry.get("kind") not in _KINDS:
55
+ raise ValueError(f"ZERO FALLBACK: {_SOURCE} gives dtype {name!r} kind {entry.get('kind')!r} — one of "
56
+ f"{', '.join(_KINDS)}")
57
+ for name, entry in packed.items():
58
+ for key in ("bits", "group_size", "scale_dtype", "min_dtype"):
59
+ if key not in entry:
60
+ raise KeyError(f"ZERO FALLBACK: {_SOURCE} packed encoding {name!r} states no {key}")
61
+ safetensors = {e["safetensors"]: n for n, e in dense.items() if "safetensors" in e}
62
+ keys = {k: n for n, e in dense.items() for k in (e.get("autotune_keys") or ())}
63
+ return dense, packed, safetensors, keys
64
+
65
+
66
+ def canonical_name(dtype: Any) -> str:
67
+ """The table's name for `dtype` (not checked against the table: `itemsize` refuses)."""
68
+ if isinstance(dtype, Enum):
69
+ name = dtype.name
70
+ if name.endswith("_"): # `bool_`: Python's keyword escape, not a dtype name
71
+ name = name[:-1]
72
+ elif isinstance(dtype, str):
73
+ name = dtype
74
+ elif type(dtype).__module__ == "torch": # a torch.dtype: its str() is its torch spelling
75
+ name = str(dtype)
76
+ else:
77
+ raise TypeError(f"ZERO FALLBACK: {dtype!r} ({type(dtype).__name__}) is not a dtype name, "
78
+ f"a torch dtype or a dtype enum member ({_SOURCE})")
79
+ return name[len(_TORCH_PREFIX):] if name.startswith(_TORCH_PREFIX) else name
80
+
81
+
82
+ def _dense_entry(dtype: Any) -> Tuple[str, Dict[str, Any]]:
83
+ dense, packed, _, _ = _tables()
84
+ name = canonical_name(dtype)
85
+ if name in dense:
86
+ return name, dense[name]
87
+ if name in packed:
88
+ raise ValueError(f"ZERO FALLBACK: {name!r} is a packed encoding of {packed[name]['bits']} bits "
89
+ f"per element ({_SOURCE}) and has no whole itemsize — its bytes are "
90
+ f"representation_bytes({name!r}, numel)")
91
+ raise ValueError(f"ZERO FALLBACK: unknown dtype {dtype!r}: {_SOURCE} has no entry {name!r} "
92
+ f"(known: {', '.join(sorted(dense))}). Add it there with its source; no width "
93
+ f"is ever guessed.")
94
+
95
+
96
+ def known_dtypes() -> Tuple[str, ...]:
97
+ """Every dense dtype the table carries, sorted."""
98
+ return tuple(sorted(_tables()[0]))
99
+
100
+
101
+ def itemsize(dtype: Any) -> int:
102
+ """Bytes per element of `dtype`, refused by name when the table does not carry it."""
103
+ return _dense_entry(dtype)[1]["bits"] // 8
104
+
105
+
106
+ def kind(dtype: Any) -> str:
107
+ """The class of `dtype`: float, int, uint, bool or complex (the table's `kind`)."""
108
+ return _dense_entry(dtype)[1]["kind"]
109
+
110
+
111
+ def finite_max(dtype: Any) -> float:
112
+ """The largest finite value of a floating `dtype` (the table's `max`); any other kind is refused by name."""
113
+ name, entry = _dense_entry(dtype)
114
+ if entry.get("kind") != "float":
115
+ raise ValueError(f"ZERO FALLBACK: {name!r} is not a floating dtype: it has no finite maximum")
116
+ if "max" not in entry:
117
+ raise KeyError(f"ZERO FALLBACK: {_SOURCE} states no max for {name!r}")
118
+ return float(entry["max"])
119
+
120
+
121
+ def itemsize_table() -> Dict[str, int]:
122
+ """{name: bytes per element} over every dense dtype (a copy: the caller may not edit the table)."""
123
+ return {name: entry["bits"] // 8 for name, entry in _tables()[0].items()}
124
+
125
+
126
+ def from_safetensors(code: str) -> str:
127
+ """The dtype name of a safetensors header code ("BF16" -> "bfloat16"), refused by name."""
128
+ names = _tables()[2]
129
+ if code not in names:
130
+ raise ValueError(f"ZERO FALLBACK: safetensors dtype code {code!r} has no entry in {_SOURCE} "
131
+ f"(known: {', '.join(sorted(names))})")
132
+ return names[code]
133
+
134
+
135
+ def from_autotune_key(key: str) -> str:
136
+ """The dtype name of a launch-key spelling ("fp16" -> "float16", "int1" -> "bool")."""
137
+ names = _tables()[3]
138
+ if key not in names:
139
+ raise ValueError(f"ZERO FALLBACK: launch-key dtype {key!r} has no entry in {_SOURCE} "
140
+ f"(known: {', '.join(sorted(names))})")
141
+ return names[key]
142
+
143
+
144
+ def storage_dtype(dtype: Any, stores_fp64: bool) -> str:
145
+ """The dtype a tensor of `dtype` is STORED in by a store that does (`stores_fp64`) or does not
146
+ hold 64-bit floats: float64 -> float32 and complex128 -> complex64 without, unchanged with."""
147
+ name, entry = _dense_entry(dtype)
148
+ if stores_fp64:
149
+ return name
150
+ return entry.get("without_fp64", name)
151
+
152
+
153
+ def representation_bytes(dtype: Any, numel: int, split: bool = False) -> int:
154
+ """Bytes `numel` elements of `dtype` occupy as represented: a dense dtype at its itemsize, a
155
+ hi+lo SPLIT operand as two tensors of `dtype` (its low dtype), a packed encoding as its packed
156
+ values plus one scale and one minimum per group."""
157
+ numel = int(numel)
158
+ packed = _tables()[1]
159
+ name = canonical_name(dtype)
160
+ if name in packed:
161
+ if split:
162
+ raise ValueError(f"ZERO FALLBACK: a packed encoding ({name!r}) is not split hi+lo")
163
+ e = packed[name]
164
+ groups = -(-numel // int(e["group_size"]))
165
+ return -(-numel * int(e["bits"]) // 8) + groups * (itemsize(e["scale_dtype"]) + itemsize(e["min_dtype"]))
166
+ return numel * itemsize(dtype) * (2 if split else 1)
@@ -0,0 +1,72 @@
1
+ """NBXDtype and its tables: the dtype enum NBXTensor carries (its values frozen: new members are appended, never
2
+ renumbered), and the tables derived from the ONE dtype table (`dtypes.yml`): byte sizes, the spellings it parses (a
3
+ table name, a launch-key spelling, an alias such as torch's `half`; a `torch.` prefix read as text), the numpy names
4
+ and the array-interface type strings, the floating and complex kinds."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from enum import IntEnum
9
+
10
+ # ============================================================================
11
+ # DTYPE SYSTEM
12
+ # ============================================================================
13
+
14
+ class NBXDtype(IntEnum):
15
+ float16 = 0
16
+ bfloat16 = 1
17
+ float32 = 2
18
+ float64 = 3
19
+ int8 = 4
20
+ int16 = 5
21
+ int32 = 6
22
+ int64 = 7
23
+ uint8 = 8
24
+ bool_ = 9
25
+ complex64 = 10
26
+ complex128 = 11
27
+
28
+
29
+ def _table_name(m: "NBXDtype") -> str:
30
+ """A member's name in the dtype table (`bool_` is Python's keyword escape of `bool`)."""
31
+ return "bool" if m is NBXDtype.bool_ else m.name
32
+
33
+
34
+ def _derive():
35
+ """Every table of the enum, read from the one dtype table (`dtypes.yml`): widths, the spellings a caller may name
36
+ a dtype by, the numpy names, the array-interface type strings, the floating and complex kinds."""
37
+ from .itemsize import dtype_table
38
+ entries = dtype_table()["dtypes"]
39
+ sizes, names, numpy_names, typestr, floating, complex_ = {}, {}, {}, {}, set(), set()
40
+ for m in NBXDtype:
41
+ e = entries[_table_name(m)]
42
+ sizes[m] = int(e["bits"]) // 8
43
+ for spelling in [_table_name(m), *(e.get("autotune_keys") or ()), *(e.get("aliases") or ())]:
44
+ names[spelling] = m
45
+ if "numpy" in e:
46
+ numpy_names[e["numpy"]] = m
47
+ typestr[m] = e["typestr"]
48
+ if e["kind"] == "float":
49
+ floating.add(m)
50
+ elif e["kind"] == "complex":
51
+ complex_.add(m)
52
+ return sizes, names, numpy_names, typestr, frozenset(floating), frozenset(complex_)
53
+
54
+
55
+ # Read once from the table: no width, spelling or type string is written here (R-NBXT 2, one fact once).
56
+ _DTYPE_SIZES, _DTYPE_FROM_STR, _NUMPY_DTYPE_MAP, _DTYPE_TYPESTR, _FLOATING_DTYPES, _COMPLEX_DTYPES = _derive()
57
+
58
+
59
+ def dtype_size(dtype: NBXDtype) -> int:
60
+ return _DTYPE_SIZES[dtype]
61
+
62
+
63
+ def parse_dtype(d) -> NBXDtype:
64
+ """Parse dtype from string or NBXDtype."""
65
+ if isinstance(d, NBXDtype):
66
+ return d
67
+ if isinstance(d, str):
68
+ s = d.replace('torch.', '')
69
+ return _DTYPE_FROM_STR[s]
70
+ # Unknown type — try string conversion
71
+ s = str(d).replace('torch.', '')
72
+ return _DTYPE_FROM_STR[s]
@@ -0,0 +1,158 @@
1
+ """The operand decisions of the kernels, as dtype names (DESIGN rule 12: the mechanism in the library): the dtype a
2
+ contraction's partials are summed in, how a matrix unit carries an operand, how an attention's operands are aligned
3
+ when they disagree, the scale of a split representation. Pure functions of dtype names and of a device's matrix unit
4
+ (`ops._configs.matrix_unit`); the application decides which device and which policy, never these rules.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Optional
9
+
10
+ from nbxtensor.dtype import itemsize as _itemsize
11
+
12
+ __all__ = ["contraction_accumulator_dtype", "contraction_accumulator_bytes", "matrix_unit_representation",
13
+ "matrix_unit_flash_representation", "ATTENTION_OPERAND_ALIGNMENTS", "attention_operand_alignment",
14
+ "attention_operand_dtypes", "matrix_unit_split_scale", "constant_load_dtype"]
15
+
16
+
17
+ def contraction_accumulator_dtype(dtype: str) -> str:
18
+ """The dtype (a name) the partial results of a contraction split over the axis it reduces
19
+ are summed in, when a stretch runs in slices of that axis (`core/strategies/chunked_piece`):
20
+ the split contraction is the SAME contraction, so its partials are summed in the accumulator
21
+ its kernel keeps — float32 for a half dtype (every mm/bmm/conv kernel accumulates fp32, and
22
+ `sum` is an AMP_FP32 op), the dtype itself for float32 and float64 — and the sum is stored
23
+ once, in the dtype the contraction's own output was given. A partial summed at the pass
24
+ dtype rounds once per slice where the whole op rounds once. Any other dtype is refused by
25
+ name: a contraction over a token axis yields a floating value.
26
+
27
+ The one definition both engines read."""
28
+ if dtype in ("float16", "bfloat16"):
29
+ return "float32"
30
+ if dtype in ("float32", "float64"):
31
+ return dtype
32
+ raise ValueError(f"contraction_accumulator_dtype: {dtype!r} is not a floating dtype a "
33
+ f"contraction's partials are summed in")
34
+
35
+
36
+ def contraction_accumulator_bytes(store_bytes: int) -> int:
37
+ """`contraction_accumulator_dtype` read on a WIDTH (bytes per element), for Prism, which prices
38
+ a plan in widths: a 2-byte store (fp16 or bf16 alike) accumulates in 4 bytes; 4 and 8 are their
39
+ own accumulator. The same rule (a unit test holds the two equal over every floating dtype)."""
40
+ if store_bytes == 2:
41
+ return 4
42
+ if store_bytes in (4, 8):
43
+ return store_bytes
44
+ raise ValueError(f"contraction_accumulator_bytes: a {store_bytes}-byte store is no floating "
45
+ f"dtype a contraction's partials are summed in")
46
+
47
+
48
+ def matrix_unit_representation(dtype: str, unit: dict) -> Optional[str]:
49
+ """How the hardware profile's matrix unit (`matrix_unit`, nbxtensor.ops._configs.matrix_unit) carries one GEMM
50
+ operand whose dtype in memory is `dtype` (a name): "native" when it IS the unit's operand dtype (one MMA term
51
+ per operand), "split" when the profile declares `matrix_unit.fp32_split` with this dtype among its `operands`
52
+ (the value, widened to fp32, is carried as hi + lo of the operand dtype under a power-of-two scale, see
53
+ `matrix_unit_split_scale`; hi*hi + hi*lo + lo*hi with fp32 accumulation outside the unit, the vendor's fp32
54
+ arithmetic kept), else None (the GEMM keeps its tl.dot kernel). The ONE decision: the wrappers launch with it
55
+ and the derived census keys with it (nbxtensor.ops.launch_keys.matrix_unit_operands)."""
56
+ if not unit or not unit.get("mm"):
57
+ return None
58
+ if dtype == unit["operand_dtype"]:
59
+ return "native"
60
+ split = unit.get("fp32_split") or {}
61
+ if split.get("mm") and dtype in (split.get("operands") or ()):
62
+ return "split"
63
+ return None
64
+
65
+
66
+ def matrix_unit_flash_representation(q: str, k: str, v: str, unit: dict) -> Optional[str]:
67
+ """How the profile's matrix unit carries an attention's three operands (names, already aligned by
68
+ `attention_operand_dtypes`) in its flash kernel (nbxtensor/ops/flash_attention_m8n8k4.py): "native" when all three
69
+ ARE the unit's operand dtype and the profile lists `matrix_unit.flash` rows; "split" when all three are among
70
+ `matrix_unit.fp32_split.operands` and the profile lists `matrix_unit.fp32_split.flash` rows — Q, K, V and the
71
+ softmax's P carried as hi + lo as `matrix_unit_representation` carries a GEMM operand, the vendor's fp32
72
+ arithmetic kept; else None (the attention keeps its tl.dot kernel). The ONE decision the wrapper launches with
73
+ and the derived census routes with (nbxtensor.ops.launch_keys.unit_flash)."""
74
+ if not unit or not (q == k == v):
75
+ return None
76
+ if q == unit["operand_dtype"]:
77
+ return "native" if unit.get("flash") else None
78
+ split = unit.get("fp32_split") or {}
79
+ if split.get("flash") and q in (split.get("operands") or ()):
80
+ return "split"
81
+ return None
82
+
83
+
84
+ #: The ways a hardware profile may align attention operands that disagree in dtype
85
+ #: (`precision.attention_operands`): every operand takes the narrowest of the three, or the widest.
86
+ ATTENTION_OPERAND_ALIGNMENTS = ("narrowest", "widest")
87
+ #: The operand dtypes the alignment chooses among; their widths are the dtype table's.
88
+ _ATTENTION_OPERAND_DTYPES = ("float16", "bfloat16", "float32", "float64")
89
+
90
+
91
+ def attention_operand_alignment(profile: dict) -> str:
92
+ """The alignment the hardware profile declares for attention operands that disagree in dtype
93
+ (`precision.attention_operands`, one of ATTENTION_OPERAND_ALIGNMENTS). A profile that declares
94
+ none, or another word, is refused by name: the choice moves a whole attention between the
95
+ matrix unit and the scalar units, so it is never guessed.
96
+
97
+ The one definition both engines read."""
98
+ v = (profile.get("precision") or {}).get("attention_operands")
99
+ if v not in ATTENTION_OPERAND_ALIGNMENTS:
100
+ raise ValueError(f"hardware profile {profile.get('architecture')!r}: "
101
+ f"precision.attention_operands is {v!r}, not one of "
102
+ f"{ATTENTION_OPERAND_ALIGNMENTS}")
103
+ return v
104
+
105
+
106
+ def attention_operand_dtypes(q: str, k: str, v: str, alignment: str,
107
+ q_round: Optional[str] = None) -> tuple:
108
+ """The (q, k, v, q_round) dtypes (names) an attention computes with — the ONE decision both
109
+ engines' attention reads (R30): the Triton wrapper (`launch_keys.sdpa_operand_dtypes`), the
110
+ derived census and Prism's width estimate through it, and the compiled engine through its
111
+ twin. The kernels need matching operands: three that already match (Q judged at its KV-cache
112
+ rounding `q_round` when the cache asks for one) are returned unchanged; three that disagree
113
+ all take the narrowest or the widest of them, as the profile's `alignment` declares
114
+ (`attention_operand_alignment`), the first in (q, k, v) order on a tie, and q_round is
115
+ dropped. A non-floating operand is refused by name.
116
+
117
+ Measured on V100 (2026-10-08, nbx/campaigns/2026_10_08_allegro_prof/probe): an fp16 attention
118
+ whose V alone came out of an fp32-stored GEMM ran 22.0 s/op on the scalar flash when aligned
119
+ to the widest, 1.16 s/op on the matrix-unit flash when aligned to the narrowest.
120
+
121
+ The one definition both engines read."""
122
+ qj = q_round if q_round is not None else q
123
+ for d in (qj, k, v):
124
+ if d not in _ATTENTION_OPERAND_DTYPES:
125
+ raise ValueError(f"attention_operand_dtypes: {d!r} is not a floating dtype an "
126
+ f"attention computes in")
127
+ if qj == k == v:
128
+ return q, k, v, q_round
129
+ if alignment not in ATTENTION_OPERAND_ALIGNMENTS:
130
+ raise ValueError(f"attention_operand_dtypes: alignment {alignment!r} is not one of "
131
+ f"{ATTENTION_OPERAND_ALIGNMENTS}")
132
+ pick = min if alignment == "narrowest" else max
133
+ t = pick((qj, k, v), key=_itemsize.itemsize)
134
+ return t, t, t, None
135
+
136
+
137
+ def matrix_unit_split_scale(unit: dict) -> tuple:
138
+ """(hi_exp, lo_shift) of the split representation, derived from the unit's operand dtype (never written in a
139
+ kernel): each operand tile is scaled by the power of two that brings its largest magnitude into
140
+ [2^hi_exp, 2^(hi_exp+1)) — the top binade below the operand dtype's overflow, so no hi overflows and every
141
+ value down to 2^-(hi_exp+1+|min exponent|) of the tile's largest stays normal — and lo, the residual
142
+ x - hi, is stored times 2^lo_shift (the operand dtype's significand width) so it does not underflow where hi
143
+ does not (Ootomo & Yokota 2022, arXiv 2203.03341, eq. 19-24)."""
144
+ import numpy as np
145
+ fi = np.finfo(np.dtype(unit["operand_dtype"]))
146
+ return int(fi.maxexp) - 2, int(fi.nmant) + 1
147
+
148
+
149
+ def constant_load_dtype(traced: str, compute: str, stores_fp64: bool) -> str:
150
+ """The dtype (a name) an embedded graph constant is bound in by the Triton engines
151
+ (`GraphExecutor._load_constant_triton`): a bfloat16 constant is decoded to the compute dtype
152
+ when that is half (fp16 bits from bf16 on hardware computing fp16, the bf16 bits kept under
153
+ bf16), to float32 otherwise; float64 / complex128 are held as the branch holds them
154
+ (`dtype.itemsize.storage_dtype`); every other dtype is kept as traced. The loader and Prism's width
155
+ pass both ask it."""
156
+ if traced == "bfloat16":
157
+ return compute if compute in ("float16", "bfloat16") else "float32"
158
+ return _itemsize.storage_dtype(traced, stores_fp64)
@@ -0,0 +1,64 @@
1
+ """The output dtype of an op (DESIGN rule 12: the mechanism in the library): read from the op's traced record, or
2
+ decided by the cast-back rule of an op computed in float32 inside a half forward. Pure functions of dtype names and of
3
+ a plain dict; the one definition every engine imports (an engine holding torch dtypes, NBXDtype or strings reads the
4
+ same answer: only the last dotted segment of a dtype's name is read).
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Dict, FrozenSet, Optional
9
+
10
+ __all__ = ["COMPLEX_DTYPE_NAMES", "traced_output_dtype_name", "traced_output_is_complex", "amp_fp32_output_dtype"]
11
+
12
+ COMPLEX_DTYPE_NAMES: FrozenSet[str] = frozenset({"complex32", "complex64", "complex128", "chalf", "cfloat", "cdouble"})
13
+ _GRAPH_FLOAT_NAMES: FrozenSet[str] = frozenset({"float16", "bfloat16", "float32", "float64"})
14
+ _HALF_GRAPH_NAMES: FrozenSet[str] = frozenset({"float16", "bfloat16"})
15
+
16
+
17
+ def _name(dtype) -> str:
18
+ return str(dtype).rsplit(".", 1)[-1]
19
+
20
+
21
+ def traced_output_dtype_name(record: Optional[Dict[str, Any]]) -> Optional[str]:
22
+ """The plain name of the op's first traced output dtype (`output_dtypes[0]` at the record's TOP level), or None
23
+ when the record states none."""
24
+ if not isinstance(record, dict):
25
+ return None
26
+ dts = record.get("output_dtypes") or ()
27
+ return _name(dts[0]) if dts else None
28
+
29
+
30
+ def traced_output_is_complex(record: Optional[Dict[str, Any]]) -> bool:
31
+ """True when the op's traced record types an output complex.
32
+
33
+ The traced OUTPUT dtype is the only signal: an op's name cannot answer (a plain `mul` by the scalar `1j` produces
34
+ complex), nor can its inputs (that scalar is no input tensor). It matters because a half input at a
35
+ complex-producing op yields complex32, which `exp`, `angle` and the inverse real FFT have no kernel for. Only
36
+ `output_dtypes` at the record's TOP level is read: a nested `attributes` dict reads as "not complex"."""
37
+ if not isinstance(record, dict):
38
+ return False
39
+ return any(_name(d) in COMPLEX_DTYPE_NAMES for d in (record.get("output_dtypes") or ()))
40
+
41
+
42
+ def amp_fp32_output_dtype(compute_dtype: str, graph_dtype: Optional[str], safe: bool,
43
+ narrowed: bool, traced: Optional[str] = None) -> str:
44
+ """The OUTPUT dtype (a name) of an op computed in float32 inside under the half compute dtype `compute_dtype`.
45
+
46
+ * bfloat16 on a bfloat16 graph (`graph_dtype`, the dtype the model was traced in): bfloat16 — a float32 result
47
+ always fits bfloat16's range, and the model's own forward returns its input's dtype there.
48
+ * bfloat16 on a graph of another floating dtype (a float32 model computed in bfloat16): float32, the dtype the
49
+ model's forward returned. A graph dtype that is no floating dtype is refused by name.
50
+ * float16: float16 only under the precision contract — `safe` (narrows every such op) or `narrowed` (this op is
51
+ in the narrowed set); float32 otherwise. An op `traced` in float32 inside a half graph is the model's own
52
+ float32 island: only `narrowed` brings it to float16, never `safe`."""
53
+ if compute_dtype == "bfloat16":
54
+ if graph_dtype not in _GRAPH_FLOAT_NAMES:
55
+ raise ValueError(
56
+ f"amp_fp32_output_dtype: under bfloat16 compute the output follows the graph dtype, and "
57
+ f"{graph_dtype!r} is none of {sorted(_GRAPH_FLOAT_NAMES)} — the traced graph must state it")
58
+ return "bfloat16" if graph_dtype == "bfloat16" else "float32"
59
+ if compute_dtype == "float16":
60
+ if traced == "float32" and graph_dtype in _HALF_GRAPH_NAMES:
61
+ return "float16" if narrowed else "float32"
62
+ return "float16" if (safe or narrowed) else "float32"
63
+ raise ValueError(f"amp_fp32_output_dtype: compute dtype {compute_dtype!r} is not a half dtype — the cast-back "
64
+ f"rule exists for bfloat16 and float16 only")