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,379 @@
1
+ Metadata-Version: 2.5
2
+ Name: nbxtensor
3
+ Version: 0.0.1
4
+ Summary: An inference-first tensor library, torch-free: planned memory, Triton kernels that declare their cost, CUDA / Apple Metal / CPU backends.
5
+ Project-URL: Homepage, https://github.com/NeuroBrix/nbxtensor
6
+ Project-URL: Source, https://github.com/NeuroBrix/nbxtensor
7
+ Project-URL: Issues, https://github.com/NeuroBrix/nbxtensor/issues
8
+ Project-URL: Changelog, https://github.com/NeuroBrix/nbxtensor/blob/main/CHANGELOG.md
9
+ Author: NeuroBrix
10
+ License: Apache-2.0
11
+ License-File: LICENSE
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: numpy
14
+ Requires-Dist: nvidia-cuda-runtime-cu12; platform_system == 'Linux'
15
+ Requires-Dist: pyyaml
16
+ Requires-Dist: triton
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest; extra == 'test'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # nbxtensor
22
+
23
+ An inference-first tensor library, torch-free.
24
+
25
+ PyTorch is a general framework built for research and training in eager mode: dynamic graphs, autograd, a generic
26
+ dispatcher, a caching allocator that discovers memory as the program runs. Inference is a narrower problem, and a
27
+ better-informed one: the graph and the shapes are known before the first run. nbxtensor is built for that case.
28
+
29
+ - **Memory is planned, not discovered.** Allocators per device behind one interface, and a static planner that
30
+ assigns every buffer before the first run.
31
+ - **Every kernel declares its cost.** Outputs, workspace and scratch with their lifetimes, bytes moved, flops. A
32
+ planner prices a model from these declarations instead of guessing what the allocator will do.
33
+ - **Capture and replay are first-class.** A repeated sequence of launches is recorded, verified byte-equal and
34
+ replayed.
35
+ - **One table of facts.** One dtype table, one view rule, one conversion per pair of dtypes, one capability table per
36
+ backend — declared, never guessed.
37
+ - **Backends:** NVIDIA CUDA, Apple Metal (through triton-ext), CPU.
38
+ - **No torch, ever.** nbxtensor never imports torch, not even optionally. Exchange with any tensor library goes
39
+ through the DLPack protocol (`__dlpack__`, `from_dlpack`), so a torch user writes `torch.from_dlpack(t)` on their
40
+ side.
41
+
42
+ nbxtensor is extracted from NeuroBrix, the universal inference engine whose Triton engine is its first client. It
43
+ lives in `nbxtensor/` of the NeuroBrix repository until the extraction is complete, then in its own repository.
44
+
45
+ ## Status
46
+
47
+ Every area below is in the library, and every public name is documented in `docs/nbxtensor/API.md` (the list of
48
+ undocumented names is empty and a test keeps it so). This README documents what is in the library TODAY; every example below is executed by
49
+ `nbxtensor/tests/test_readme.py`, so an example that stops working fails a test. The target API is
50
+ `docs/nbxtensor/API.md`.
51
+
52
+ | area | in the library | what it holds |
53
+ |---|---|---|
54
+ | `nbxtensor.dtype` | yes | the dtype table (`dtypes.yml`) and its reader, the enum's tables derived from it, host bfloat16 conversions, the promotion rules |
55
+ | `nbxtensor.shape` | yes | numel, strides, contiguity, the view rule, broadcasting, slice bounds |
56
+ | `nbxtensor.random` | yes | seeded random streams and draws |
57
+ | `nbxtensor.tensor`, `memory`, `backend`, `ops`, `quant` | yes | the tensor class, the allocators, the device backends, the Triton kernels and their cost declarations |
58
+ | `nbxtensor.host` | yes | the services still passed by an application while the boundary is cleaned (below; API.md section 11) |
59
+
60
+ What changed from one version to the next — added, changed, fixed, removed — is in [`CHANGELOG.md`](https://github.com/NeuroBrix/nbxtensor/blob/main/CHANGELOG.md).
61
+
62
+ ## Installation
63
+
64
+ nbxtensor never needs torch: it is not a dependency, not even an optional one, and the library and its kernels run in
65
+ an environment where torch is not installed (proven on Metal, 2026-10-29: `tools/torch_free_proof.py` records every
66
+ attempt to import torch, caught ones included, and recorded none). Python 3.10 or later; its dependencies are numpy,
67
+ pyyaml and Triton.
68
+
69
+ ```
70
+ pip install -e nbxtensor # from the NeuroBrix repository
71
+ ```
72
+
73
+ **Linux, NVIDIA (CUDA).** Triton comes from PyPI with the install above.
74
+
75
+ **macOS, Apple silicon (Metal).** PyPI publishes no Triton for macOS, so Triton and its Metal backend are built from
76
+ source, then nbxtensor is installed into that environment:
77
+
78
+ 1. Triton (github.com/triton-lang/triton) built with `TRITON_EXT_ENABLED=1`, which lets out-of-tree backends plug in;
79
+ 2. the AppleGPU backend of triton-ext (github.com/triton-lang/triton-ext, `backend/AppleGPU`), installed into the same
80
+ environment;
81
+ 3. `pyobjc-framework-Metal` (the library opens the Metal device through it);
82
+ 4. Xcode's Metal toolchain (`xcrun metal` must find the real compiler, not Xcode's placeholder: `TOOLCHAINS=Metal`).
83
+
84
+ `nbxtensor.dtype` and `nbxtensor.shape` need neither a device nor Triton.
85
+
86
+ ## Concepts
87
+
88
+ **Plain data at the edges.** A dtype is named by its table name (`"float16"`, `"bfloat16"` …), a shape is a tuple of
89
+ ints, strides are a tuple of ints counted in ELEMENTS (never bytes), a device is a kind and an index. Nothing in the
90
+ API takes a duck-typed object or keeps state in a module global: the same API is meant to be implemented later by a
91
+ Rust core.
92
+
93
+ **One table.** Every width, safetensors code, launch-key spelling and kind of a dtype is one entry of `dtypes.yml`.
94
+ A name the table does not carry is refused by name; no width is ever guessed.
95
+
96
+ **Zero fallback.** A missing fact is an error that names it, never a default.
97
+
98
+ ## Examples
99
+
100
+ ### Dtype widths and kinds
101
+
102
+ ```python
103
+ from nbxtensor.dtype import itemsize as I
104
+
105
+ assert I.itemsize("bfloat16") == 2
106
+ assert I.itemsize("complex64") == 8
107
+ assert I.kind("bfloat16") == "float" and I.kind("uint8") == "uint"
108
+ assert I.from_safetensors("BF16") == "bfloat16"
109
+ assert I.from_autotune_key("fp16") == "float16"
110
+ # a store without float64 holds float32 in its place
111
+ assert I.storage_dtype("float64", stores_fp64=False) == "float32"
112
+ # a packed int4 weight with float16 scales and minima per group of 128
113
+ assert I.representation_bytes("int4-g128-asym", 256) == 128 + 2 * (2 + 2)
114
+ try:
115
+ I.itemsize("fp8_unknown")
116
+ except ValueError as e:
117
+ assert "unknown dtype" in str(e)
118
+ ```
119
+
120
+ ### bfloat16 on the host
121
+
122
+ numpy has no bfloat16: a bfloat16 array on the host is its 16-bit patterns (`uint16`).
123
+
124
+ ```python
125
+ import numpy as np
126
+ from nbxtensor.dtype import convert
127
+
128
+ x = np.array([1.0, 3.14159, -2.5e-3, np.nan], dtype=np.float32)
129
+ bits = convert.f32_to_bf16_bits(x) # round to nearest, ties to even
130
+ assert bits.dtype == np.uint16 and bits[3] == 0x7FC0 # every NaN becomes the quiet NaN, as torch does
131
+ back = convert.bf16_bits_to_f32(bits) # exact widening
132
+ assert back[0] == 1.0 and abs(back[1] - 3.14159) < 0.02
133
+ half = convert.bf16_bits_to_f16(bits) # bfloat16 -> float16, through float32
134
+ assert half.dtype == np.float16
135
+
136
+ # a caller splitting a large array across workers writes each span into one output
137
+ out = np.empty(x.size, dtype=np.float16)
138
+ convert.bf16_bits_to_f16(bits[:2], out=out[:2])
139
+ convert.bf16_bits_to_f16(bits[2:], out=out[2:])
140
+ assert np.array_equal(out[:3], half[:3])
141
+ ```
142
+
143
+ ### Type promotion
144
+
145
+ ```python
146
+ from nbxtensor.dtype.promotion import promoted, float_math_input
147
+
148
+ assert promoted(["float16", "float32"]) == "float32" # the widest float, past two bytes
149
+ assert promoted(["float16", "bfloat16"]) is None # two half dtypes: the caller's precision policy decides
150
+ assert float_math_input("int64") == "float32" # rsqrt of an integer reads it as float32
151
+
152
+ from nbxtensor.dtype.promotion import binary_result
153
+ assert binary_result("int8", "uint8") == "int16" # the Array API standard: wide enough for both
154
+ assert binary_result("complex64", "float64") == "complex128" # a complex keeps the wider float's precision
155
+ assert binary_result("int64", "float16") == "float16" # across kinds (left open by the standard): PyTorch's rule
156
+ assert binary_result("bfloat16", "float16") == "float32" # two half formats meet in float32, whatever the order
157
+ ```
158
+
159
+ ### Precision by policy
160
+
161
+ The dtype an op computes in is decided by a `Policy`: plain, immutable data handed to the calls that use it (no
162
+ context manager, no global switch). Its op classes are PyTorch's autocast tables (`nbxtensor/dtype/policy.yml`):
163
+ some ops compute in float32 whatever the policy, some in its compute dtype, some in the widest of their inputs; an op
164
+ in no class keeps its inputs' dtypes.
165
+
166
+ ```python
167
+ from nbxtensor.dtype.policy import get_policy
168
+
169
+ p = get_policy("params=float32,compute=float16,output=float32") # the spelling of JAX's jmp
170
+ assert p.compute_dtype("mm", ["float32", "float32"]) == "float16" # a matrix product: the compute dtype
171
+ assert p.compute_dtype("softmax", ["float16"]) == "float32" # a softmax: float32, whatever the policy
172
+ assert p.compute_dtype("addcmul", ["float16", "float32", "float16"]) == "float32" # the widest input
173
+ assert p.fill_value("masked_fill", -3.39e38, "float16") == -65504.0 # a bfloat16 mask sentinel fits float16
174
+ assert p.replace(compute="bfloat16").compute == "bfloat16" # a policy never changes: a new one is made
175
+ ```
176
+
177
+ An op's output dtype under a half compute dtype, when it computes in float32 inside (`nbxtensor.dtype.outputs`):
178
+
179
+ ```python
180
+ from nbxtensor.dtype.outputs import amp_fp32_output_dtype, traced_output_is_complex
181
+
182
+ assert amp_fp32_output_dtype("bfloat16", "bfloat16", safe=False, narrowed=False) == "bfloat16" # follows the graph
183
+ assert amp_fp32_output_dtype("float16", "float16", safe=False, narrowed=False) == "float32" # no contract: float32
184
+ assert traced_output_is_complex({"output_dtypes": ["complex64"]}) # read from the traced output, never the op's name
185
+ ```
186
+
187
+ ### Array API: type promotion and Python scalars
188
+
189
+ Type promotion follows the [Array API standard](https://data-apis.org/array-api/draft/API_specification/type_promotion.html),
190
+ and a Python scalar is weak (it takes the array's dtype), as the standard and NumPy's NEP 50 say. Measured by
191
+ `nbxtensor/tests/test_array_api_promotion.py`: the standard's promotion tables, 120 of 120 pairs of the dtypes the
192
+ library holds, in both operand orders, and the order-independence of the result over three operands; its scalar rules.
193
+
194
+ What the standard leaves open, decided once:
195
+
196
+ | case | nbxtensor | note |
197
+ |---|---|---|
198
+ | arrays of two kinds (bool, unsigned, signed, float, complex) | the higher kind, its own width | as PyTorch |
199
+ | float16 with bfloat16 (the standard defines neither) | float32, whatever the order | as PyTorch |
200
+ | a signed integer with uint64 | refused by name | no signed integer holds it |
201
+ | a Python float with an integer or bool array | float64 | NEP 50; PyTorch gives float32 |
202
+ | a Python complex with an integer array; with a half float array | complex128; complex64 | NEP 50; the table has no complex32 |
203
+ | a Python int outside an integer array's range | refused by name | never a silent wrap |
204
+
205
+ The gap: nbxtensor exposes no array namespace (`__array_namespace__`) yet, so the standard's own test suite
206
+ (`array-api-tests`) cannot run against it; the conformance above is measured on the standard's tables.
207
+
208
+ ```python
209
+ from nbxtensor.dtype.promotion import binary_result, scalar_result
210
+
211
+ assert binary_result("uint8", "int8") == "int16" # wide enough for both
212
+ assert scalar_result("float16", 0.5) == "float16" # a Python scalar is weak
213
+ assert scalar_result("int32", 1.5) == "float64" # left open by the standard: NEP 50
214
+ ```
215
+
216
+ ### Shapes, strides and views
217
+
218
+ ```python
219
+ from nbxtensor import shape as S
220
+
221
+ assert S.numel((2, 3, 4)) == 24
222
+ assert S.contiguous_strides((2, 3, 4)) == (12, 4, 1)
223
+
224
+ # the view rule (ATen's computeStride): merging contiguous dims is a view ...
225
+ assert S.view_strides((2, 3, 4), (12, 4, 1), (6, 4)) == [4, 1]
226
+ # ... flattening a transposed layout is not: None means "copy first"
227
+ assert S.view_strides((4, 3), (1, 4), (12,)) is None
228
+
229
+ # a dim of extent 1 may carry any stride: this decode-step head block is contiguous
230
+ assert S.is_contiguous((1, 32, 64, 1), (2048, 64, 1, 64))
231
+
232
+ assert S.broadcast_shapes((5, 3, 1), (4,)) == (5, 3, 4)
233
+ assert S.broadcast_strides((3, 1), (1, 1), (2, 3, 4)) == ((2, 3, 4), (0, 1, 0)) # expand without a copy
234
+ assert S.slice_bounds(50, 80, 60) == (50, 60) # clamped, as numpy does
235
+ ```
236
+
237
+ ### The dtype enum and its tables
238
+
239
+ ```python
240
+ from nbxtensor.dtype.nbx_dtype import NBXDtype, dtype_size, parse_dtype
241
+
242
+ assert NBXDtype.bfloat16.value == 1 # values are frozen: new members are appended
243
+ assert dtype_size(NBXDtype.complex64) == 8 # read from the one table, never written twice
244
+ assert parse_dtype("torch.float16") is NBXDtype.float16 # a torch spelling, read as text
245
+ assert parse_dtype("half") is NBXDtype.float16 # an alias the table carries
246
+ ```
247
+
248
+ ## What the library reads by itself, and what you may set
249
+
250
+ The library measures the facts of the machine it runs on and has a default for every setting, so `import nbxtensor`
251
+ works with no application around it:
252
+
253
+ - **device descriptions**: what the kernels are tiled by — shared memory, registers, warp size, tiles, the matrix
254
+ unit, dispatch limits — and how a device is recognised, one file per device in the package
255
+ (`nbxtensor/backend/devices/<vendor>/<name>.yml`, read by `nbxtensor.backend.device_files`);
256
+ - **measured facts**: the host's free memory (`nbxtensor.backend.host_memory.memory_state`), whether the device
257
+ shares the host's memory, whether its kernels compute float64 (`nbxtensor.backend.device_facts`);
258
+ - **settings with a default**, set with plain data: the tiling table the memory splits are cut by
259
+ (`nbxtensor.ops.tiling_policy.set_tiling`, default: the library's `ops/tiling.yml`), the directory of proven kernel
260
+ settings (`nbxtensor.ops.autotune_settings.set_store`, a path or a callable returning one, default: your cache, `~/.cache/nbxtensor/certified/`,
261
+ filled by the autotune at first use), the memory limit a process holds to (`nbxtensor.memory.set_limit`, default:
262
+ the physical card). `None` restores a default.
263
+
264
+ ```python
265
+ from pathlib import Path
266
+ from nbxtensor.backend.device_names import arch_string
267
+ from nbxtensor.backend.host_memory import memory_state
268
+ from nbxtensor.ops import autotune_settings, tiling_policy
269
+ from nbxtensor.backend import device_files
270
+
271
+ m4 = device_files.read("apple", "apple_m4_pro") # one device's description, from the package itself
272
+ assert m4["compute_capability"] == "apple-m4-pro" and m4["memory"]["max_shared_memory_per_block"] == 32768
273
+
274
+ assert arch_string("Apple M4 Pro") == "apple-m4-pro" # a device's key: the library's own rule
275
+ assert memory_state().source # the host reading names where it came from
276
+
277
+ autotune_settings.set_store("/tmp/my-proven-settings") # your own store of proven kernel settings
278
+ assert autotune_settings.directory() == Path("/tmp/my-proven-settings")
279
+ autotune_settings.set_store(None) # back to the default in your cache
280
+
281
+ table = tiling_policy.tiling_table() # the split sizes in force (a copy)
282
+ tiling_policy.set_tiling(table) # a whole table, as tiling.yml writes it
283
+ tiling_policy.set_tiling(None) # back to the library's file
284
+ ```
285
+
286
+ A memory limit is a setting, not a service: `nbxtensor.memory.set_limit` holds the process to a card of that many
287
+ bytes (the allocator refuses what such a card refuses, and its pool parks a fraction of the limit); absent, the
288
+ physical card is the limit.
289
+
290
+ ```python
291
+ from nbxtensor import memory
292
+
293
+ memory.set_limit(8 << 30) # behave as a card of 8 GiB on every device
294
+ memory.set_limit(None) # the physical card again
295
+ ```
296
+
297
+ Some services are still passed through `nbxtensor.host` while the boundary is cleaned before 0.0.1 (the device
298
+ description, the operand decisions of the matrix unit and attention, and where a backend build may live).
299
+ `docs/nbxtensor/API.md` section 11 lists each one, its fate (generic in the library, or back in the application) and
300
+ its state.
301
+
302
+ An application may also watch every kernel launch without running it — a dry run, to list the kernel keys a model
303
+ forms without a device: `nbxtensor.ops.launch_observer.set_launch_observer` installs an observer (`active()`,
304
+ `record(tuned, key)`, `shadow_run(...)`); with none installed, every launch runs.
305
+
306
+ ```python
307
+ from nbxtensor.ops import launch_observer
308
+
309
+ assert launch_observer.launch_observer().active() is False # no observer: nothing runs dry
310
+ ```
311
+
312
+ ## The CPU: every core
313
+
314
+ Host work over a large array runs on a thread pool, one contiguous piece per thread, each piece a numpy call (numpy
315
+ releases the GIL in its loops). The pool's size is measured on the machine (Apple silicon: its performance cores) and
316
+ can be set; under a threshold measured for each kind of work, the work runs inline. The BLAS numpy calls is held to
317
+ the same cores: as many threads as the pool outside it, one per piece inside `parallel_for`, so the two never
318
+ oversubscribe the machine (`nbxtensor.backend.cpu.blas`: Apple's Accelerate per thread, OpenBLAS for the process).
319
+
320
+ ```python
321
+ import numpy as np
322
+ from nbxtensor.backend.cpu import pool
323
+
324
+ assert pool.threads() == pool.measured_threads() >= 1 # this machine's compute cores, measured
325
+ x = np.linspace(-3, 3, 1 << 20, dtype=np.float32)
326
+ out = np.empty_like(x)
327
+ probe = lambda n: (lambda a, b: np.exp(x[a:b], out=out[a:b])) # how the threshold for "exp" is measured
328
+ pool.parallel_for(x.size, lambda a, b: np.exp(x[a:b], out=out[a:b]), "exp", probe)
329
+ assert out.tobytes() == np.exp(x).tobytes() # the same bytes as one numpy call
330
+ pool.set_threads(2) # an application's own size
331
+ assert pool.threads() == 2
332
+ pool.set_threads(None) # back to the measurement
333
+ ```
334
+
335
+ The host conversions of a weight at load are split the same way, across the loader's workers
336
+ (`nbxtensor.io.safetensors.to_bf16_bits`: float32 or float16 rounded to bfloat16, the bytes of one call).
337
+
338
+ ## Hardware
339
+
340
+ | hardware | backend | state |
341
+ |---|---|---|
342
+ | NVIDIA Tesla V100 (Volta, sm_70) | CUDA (`CudaDriver`, `AbiMemory`) | **proven**: the library's tests and the reference models' outputs identical to their judged sha, on a 4 x V100 rack, 2026-10-29 |
343
+ | Apple M4 Pro (Apple silicon, 24 GB unified) | Metal through triton-ext (`TritonExtDriver`, `MetalMemory`) | **proven**: the library's tests, the reference models' outputs identical to their judged sha, in an environment where torch is not installed, 2026-10-29 |
344
+ | NVIDIA Ampere, Hopper | CUDA | **written, not proven here**: device files exist (`backend/devices/nvidia/`), no such card on our machines |
345
+ | Apple M1, M2, M3, M5 families and the other M4 chips | Metal | **written, not proven here**: device files exist (`backend/devices/apple/`); only the M4 Pro is measured |
346
+ | AMD Instinct (CDNA, CDNA2, CDNA3) | HIP through the CUDA-style C ABI (`AbiMemory`) | **interface ready, not tested for lack of hardware**: memory primitives and device files exist; the HIP launch driver is still to write (`docs/nbxtensor/BACKENDS.md` section 6) |
347
+ | Intel GPUs (XPU) | Level Zero (not the CUDA ABI) | **interface ready, not tested for lack of hardware**: the backend contract is documented; its `DeviceMemory` over Level Zero and its launch driver are to write (BACKENDS.md section 6) |
348
+ | a CPU | none yet | **not supported** for the kernels: host work (loading, conversions) runs with numpy on every core (`backend.cpu.pool`); a CPU kernel backend is not part of 0.0.1 |
349
+ | hardware with no Triton backend (NPUs, phones) | none | **not supported**: covering it needs a Triton backend for it and the five pieces of `docs/nbxtensor/BACKENDS.md` |
350
+
351
+ ## Backends
352
+
353
+ CUDA (NVIDIA) and Apple Metal through triton-ext; what a new backend provides is `docs/nbxtensor/BACKENDS.md`, and
354
+ `tests/nbxtensor_backend_conformance` is what it must pass. The backend in force is public API:
355
+ `nbxtensor.backend.runtime.gpu_backend()` names it ("cuda", "hip", "metal"), `gpu_runtime()` is its runtime,
356
+ `backend_table()` its entry points, `device_memory()` its memory primitives, and `set_device(tensor)` binds the
357
+ driver to a tensor's device before a launch.
358
+
359
+ ## Exchange with other libraries: DLPack
360
+
361
+ nbxtensor speaks DLPack and nothing else: `Tensor.__dlpack__` / `__dlpack_device__` and `nbxtensor.from_dlpack(x)`
362
+ for any producer. The other library imports itself (`torch.from_dlpack(t)`); nbxtensor never imports torch. Planned
363
+ with the tensor class (API.md §4).
364
+
365
+ ## Limits
366
+
367
+ - Inference only: no autograd.
368
+ - Slicing has step 1 (`narrow`); a strided slice is refused by name.
369
+ - A dtype absent from the table is refused; add it to `dtypes.yml` with its source.
370
+
371
+ ## Versions
372
+
373
+ The first publication is **0.0.1** (this version); each fix published after it increments the number (0.0.2, 0.0.3,
374
+ …), with no alpha or beta label. The rewrite of the core in Rust will be **2.0.0**. `nbxtensor.__version__` is the
375
+ installed version.
376
+
377
+ ## License
378
+
379
+ Apache 2.0.