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.
- nbxtensor/THIRD_PARTY_NOTICES.md +287 -0
- nbxtensor/__init__.py +17 -0
- nbxtensor/backend/__init__.py +1 -0
- nbxtensor/backend/capabilities.py +207 -0
- nbxtensor/backend/cpu/__init__.py +1 -0
- nbxtensor/backend/cpu/blas.py +102 -0
- nbxtensor/backend/cpu/pool.py +204 -0
- nbxtensor/backend/cpu_backend.py +162 -0
- nbxtensor/backend/device_facts.py +105 -0
- nbxtensor/backend/device_files.py +129 -0
- nbxtensor/backend/device_names.py +18 -0
- nbxtensor/backend/devices/amd/cdna.yml +228 -0
- nbxtensor/backend/devices/amd/cdna2.yml +228 -0
- nbxtensor/backend/devices/amd/cdna3.yml +227 -0
- nbxtensor/backend/devices/apple/apple_m1.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_pro.yml +308 -0
- nbxtensor/backend/devices/apple/apple_m5.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_max.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_pro.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_ultra.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m6.yml +269 -0
- nbxtensor/backend/devices/apple/apple_silicon.yml +263 -0
- nbxtensor/backend/devices/nvidia/ampere.yml +214 -0
- nbxtensor/backend/devices/nvidia/hopper.yml +213 -0
- nbxtensor/backend/devices/nvidia/volta.yml +411 -0
- nbxtensor/backend/faults.py +151 -0
- nbxtensor/backend/host_memory.py +185 -0
- nbxtensor/backend/launcher.py +2350 -0
- nbxtensor/backend/launcher_contract.py +429 -0
- nbxtensor/backend/metal_backend.py +661 -0
- nbxtensor/backend/metal_device.py +1274 -0
- nbxtensor/backend/process_memory.py +73 -0
- nbxtensor/backend/replay_graph.py +322 -0
- nbxtensor/backend/runtime.py +409 -0
- nbxtensor/backend/triton_ext_driver.py +735 -0
- nbxtensor/dtype/__init__.py +1 -0
- nbxtensor/dtype/convert.py +107 -0
- nbxtensor/dtype/dtypes.yml +70 -0
- nbxtensor/dtype/itemsize.py +166 -0
- nbxtensor/dtype/nbx_dtype.py +72 -0
- nbxtensor/dtype/operands.py +158 -0
- nbxtensor/dtype/outputs.py +64 -0
- nbxtensor/dtype/policy.py +155 -0
- nbxtensor/dtype/policy.yml +51 -0
- nbxtensor/dtype/promotion.py +112 -0
- nbxtensor/host.py +55 -0
- nbxtensor/io/__init__.py +1 -0
- nbxtensor/io/read_rate.py +197 -0
- nbxtensor/io/safetensors.py +314 -0
- nbxtensor/memory/__init__.py +9 -0
- nbxtensor/memory/allocator.py +1988 -0
- nbxtensor/memory/arena.py +75 -0
- nbxtensor/memory/device_memory.py +150 -0
- nbxtensor/memory/device_transfer.py +154 -0
- nbxtensor/memory/layout.py +43 -0
- nbxtensor/memory/memory_pool.py +191 -0
- nbxtensor/memory/metal.py +96 -0
- nbxtensor/memory/oom_advice.py +91 -0
- nbxtensor/memory/pointer_tables.py +323 -0
- nbxtensor/memory/pool.py +507 -0
- nbxtensor/memory/shadow.py +43 -0
- nbxtensor/memory/slab.py +273 -0
- nbxtensor/memory/trace_replay.py +122 -0
- nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +13610 -0
- nbxtensor/memory/vmm.py +118 -0
- nbxtensor/ops/__init__.py +1 -0
- nbxtensor/ops/_autotune_policy.py +90 -0
- nbxtensor/ops/_common.py +32 -0
- nbxtensor/ops/_configs.py +1041 -0
- nbxtensor/ops/_cost.py +231 -0
- nbxtensor/ops/abs.py +17 -0
- nbxtensor/ops/adaptive_avg_pool2d.py +161 -0
- nbxtensor/ops/add.py +96 -0
- nbxtensor/ops/addcdiv.py +29 -0
- nbxtensor/ops/addcmul.py +29 -0
- nbxtensor/ops/addmv_op.py +64 -0
- nbxtensor/ops/addr.py +68 -0
- nbxtensor/ops/all_reduce.py +83 -0
- nbxtensor/ops/amax.py +40 -0
- nbxtensor/ops/any_reduce.py +83 -0
- nbxtensor/ops/arange_op.py +27 -0
- nbxtensor/ops/argmax.py +107 -0
- nbxtensor/ops/argmin.py +93 -0
- nbxtensor/ops/atan2.py +71 -0
- nbxtensor/ops/attention_wrappers.py +1648 -0
- nbxtensor/ops/autotune_bucket.py +137 -0
- nbxtensor/ops/autotune_cache.py +430 -0
- nbxtensor/ops/autotune_certified.py +1262 -0
- nbxtensor/ops/autotune_refusals.py +380 -0
- nbxtensor/ops/autotune_settings.py +58 -0
- nbxtensor/ops/avg_pool2d.py +73 -0
- nbxtensor/ops/baddbmm_op.py +132 -0
- nbxtensor/ops/batch_norm.py +163 -0
- nbxtensor/ops/binary_strided.py +196 -0
- nbxtensor/ops/bitwise_and.py +26 -0
- nbxtensor/ops/bitwise_left_shift.py +47 -0
- nbxtensor/ops/bitwise_not.py +24 -0
- nbxtensor/ops/bitwise_or.py +26 -0
- nbxtensor/ops/bitwise_right_shift.py +47 -0
- nbxtensor/ops/bucketize.py +37 -0
- nbxtensor/ops/cat_op.py +87 -0
- nbxtensor/ops/celu.py +20 -0
- nbxtensor/ops/clamp.py +24 -0
- nbxtensor/ops/clamp_min.py +22 -0
- nbxtensor/ops/conv2d.py +265 -0
- nbxtensor/ops/conv2d_m8n8k4.py +165 -0
- nbxtensor/ops/conv_depthwise2d.py +154 -0
- nbxtensor/ops/conv_transpose2d.py +148 -0
- nbxtensor/ops/conv_wrappers.py +1482 -0
- nbxtensor/ops/copy.py +18 -0
- nbxtensor/ops/copy_op.py +39 -0
- nbxtensor/ops/cos.py +17 -0
- nbxtensor/ops/count_nonzero.py +64 -0
- nbxtensor/ops/cross_entropy.py +67 -0
- nbxtensor/ops/cummax.py +109 -0
- nbxtensor/ops/cummin.py +105 -0
- nbxtensor/ops/cumsum.py +244 -0
- nbxtensor/ops/decode_attn_vec.py +162 -0
- nbxtensor/ops/depthwise_conv2d.py +132 -0
- nbxtensor/ops/dequant_gemv.py +190 -0
- nbxtensor/ops/dft.py +67 -0
- nbxtensor/ops/dispatch.py +1228 -0
- nbxtensor/ops/div.py +75 -0
- nbxtensor/ops/dot_op.py +67 -0
- nbxtensor/ops/dropout.py +23 -0
- nbxtensor/ops/dtype_convert.py +42 -0
- nbxtensor/ops/elementwise_launch.py +81 -0
- nbxtensor/ops/elementwise_wrappers.py +2315 -0
- nbxtensor/ops/elu.py +20 -0
- nbxtensor/ops/embedding.py +69 -0
- nbxtensor/ops/eq.py +32 -0
- nbxtensor/ops/erf.py +18 -0
- nbxtensor/ops/exp.py +17 -0
- nbxtensor/ops/exp2.py +18 -0
- nbxtensor/ops/fft_op.py +125 -0
- nbxtensor/ops/fill_op.py +20 -0
- nbxtensor/ops/flash_attention.py +307 -0
- nbxtensor/ops/flash_attention_m8n8k4.py +384 -0
- nbxtensor/ops/flash_decode.py +78 -0
- nbxtensor/ops/flip_op.py +60 -0
- nbxtensor/ops/floor.py +90 -0
- nbxtensor/ops/full_op.py +24 -0
- nbxtensor/ops/fused_moe.py +417 -0
- nbxtensor/ops/fused_moe_m8n8k4.py +99 -0
- nbxtensor/ops/fused_upsample_conv.py +413 -0
- nbxtensor/ops/gather_op.py +52 -0
- nbxtensor/ops/ge.py +32 -0
- nbxtensor/ops/gelu.py +31 -0
- nbxtensor/ops/gemv_vec.py +80 -0
- nbxtensor/ops/glu.py +43 -0
- nbxtensor/ops/grid_sampler.py +145 -0
- nbxtensor/ops/group_norm_tiled.py +67 -0
- nbxtensor/ops/groupnorm.py +123 -0
- nbxtensor/ops/gt.py +32 -0
- nbxtensor/ops/hardsigmoid.py +18 -0
- nbxtensor/ops/hardswish.py +19 -0
- nbxtensor/ops/im2col.py +57 -0
- nbxtensor/ops/index_add.py +95 -0
- nbxtensor/ops/index_put_op.py +92 -0
- nbxtensor/ops/index_select.py +121 -0
- nbxtensor/ops/index_wrappers.py +770 -0
- nbxtensor/ops/isfinite.py +20 -0
- nbxtensor/ops/isinf.py +20 -0
- nbxtensor/ops/isnan.py +18 -0
- nbxtensor/ops/launch_keys.py +1054 -0
- nbxtensor/ops/launch_observer.py +54 -0
- nbxtensor/ops/layernorm.py +71 -0
- nbxtensor/ops/le.py +35 -0
- nbxtensor/ops/leaky_relu.py +19 -0
- nbxtensor/ops/lerp.py +76 -0
- nbxtensor/ops/linalg_wrappers.py +1370 -0
- nbxtensor/ops/linspace_op.py +47 -0
- nbxtensor/ops/log.py +17 -0
- nbxtensor/ops/log_sigmoid.py +23 -0
- nbxtensor/ops/logical_and.py +26 -0
- nbxtensor/ops/logical_not.py +24 -0
- nbxtensor/ops/logical_or.py +26 -0
- nbxtensor/ops/logical_xor.py +37 -0
- nbxtensor/ops/loss_wrappers.py +282 -0
- nbxtensor/ops/lt.py +32 -0
- nbxtensor/ops/masked_fill.py +21 -0
- nbxtensor/ops/matmul.py +377 -0
- nbxtensor/ops/matmul_m8n8k4.py +235 -0
- nbxtensor/ops/max_pool2d.py +74 -0
- nbxtensor/ops/max_pool3d.py +93 -0
- nbxtensor/ops/max_reduce.py +91 -0
- nbxtensor/ops/maximum.py +18 -0
- nbxtensor/ops/mean.py +40 -0
- nbxtensor/ops/min_reduce.py +91 -0
- nbxtensor/ops/minimum.py +18 -0
- nbxtensor/ops/mish.py +22 -0
- nbxtensor/ops/mma_m8n8k4.py +152 -0
- nbxtensor/ops/moe_align.py +195 -0
- nbxtensor/ops/moe_decode_vec.py +374 -0
- nbxtensor/ops/moe_device_residency.py +141 -0
- nbxtensor/ops/moe_wrappers.py +345 -0
- nbxtensor/ops/mse_loss.py +79 -0
- nbxtensor/ops/mul.py +51 -0
- nbxtensor/ops/nan_to_num.py +23 -0
- nbxtensor/ops/ne.py +32 -0
- nbxtensor/ops/neg.py +17 -0
- nbxtensor/ops/nllloss.py +124 -0
- nbxtensor/ops/nonzero.py +33 -0
- nbxtensor/ops/nonzero_op.py +62 -0
- nbxtensor/ops/norm_wrappers.py +579 -0
- nbxtensor/ops/ones_op.py +20 -0
- nbxtensor/ops/oracles/__init__.py +6 -0
- nbxtensor/ops/oracles/conv2d_fp64.py +450 -0
- nbxtensor/ops/pad_op.py +159 -0
- nbxtensor/ops/paged_decode_attn.py +215 -0
- nbxtensor/ops/pixel_shuffle.py +144 -0
- nbxtensor/ops/pixel_unshuffle.py +72 -0
- nbxtensor/ops/pow.py +137 -0
- nbxtensor/ops/prod.py +81 -0
- nbxtensor/ops/rand_op.py +52 -0
- nbxtensor/ops/random_wrappers.py +366 -0
- nbxtensor/ops/reciprocal.py +17 -0
- nbxtensor/ops/reduction_wrappers.py +845 -0
- nbxtensor/ops/relu.py +18 -0
- nbxtensor/ops/remainder.py +46 -0
- nbxtensor/ops/repeat_interleave.py +48 -0
- nbxtensor/ops/repeat_op.py +132 -0
- nbxtensor/ops/residual_chain.py +374 -0
- nbxtensor/ops/rmsnorm.py +68 -0
- nbxtensor/ops/rope.py +142 -0
- nbxtensor/ops/rsqrt.py +17 -0
- nbxtensor/ops/scaled_softmax.py +96 -0
- nbxtensor/ops/scatter_op.py +139 -0
- nbxtensor/ops/screen_oracle.py +479 -0
- nbxtensor/ops/selu.py +22 -0
- nbxtensor/ops/shape_wrappers.py +1032 -0
- nbxtensor/ops/sigmoid.py +19 -0
- nbxtensor/ops/silu.py +27 -0
- nbxtensor/ops/sin.py +17 -0
- nbxtensor/ops/softmax.py +169 -0
- nbxtensor/ops/softplus.py +27 -0
- nbxtensor/ops/sort_op.py +304 -0
- nbxtensor/ops/spectral_wrappers.py +481 -0
- nbxtensor/ops/sqrt.py +17 -0
- nbxtensor/ops/stack_op.py +84 -0
- nbxtensor/ops/std.py +113 -0
- nbxtensor/ops/strided_copy.py +200 -0
- nbxtensor/ops/sub.py +35 -0
- nbxtensor/ops/sum.py +40 -0
- nbxtensor/ops/tan.py +19 -0
- nbxtensor/ops/tanh.py +21 -0
- nbxtensor/ops/threshold.py +19 -0
- nbxtensor/ops/tiling.yml +91 -0
- nbxtensor/ops/tiling_policy.py +43 -0
- nbxtensor/ops/tiling_sizes.py +370 -0
- nbxtensor/ops/topk.py +267 -0
- nbxtensor/ops/trace_op.py +64 -0
- nbxtensor/ops/tril.py +64 -0
- nbxtensor/ops/triu.py +62 -0
- nbxtensor/ops/unfold_op.py +37 -0
- nbxtensor/ops/unique_op.py +97 -0
- nbxtensor/ops/upsample_bilinear2d.py +106 -0
- nbxtensor/ops/upsample_nearest2d.py +52 -0
- nbxtensor/ops/upsample_nearest3d.py +61 -0
- nbxtensor/ops/var.py +136 -0
- nbxtensor/ops/vector_norm.py +216 -0
- nbxtensor/ops/weight_norm.py +104 -0
- nbxtensor/ops/where.py +39 -0
- nbxtensor/ops/wrappers.py +825 -0
- nbxtensor/ops/zeros_op.py +20 -0
- nbxtensor/quant/__init__.py +1 -0
- nbxtensor/quant/quantized_tensor.py +187 -0
- nbxtensor/random/__init__.py +1 -0
- nbxtensor/random/mt_randn.py +154 -0
- nbxtensor/random/rng_pin.py +59 -0
- nbxtensor/random/rng_stream.py +118 -0
- nbxtensor/random/seeded_draw.py +223 -0
- nbxtensor/shape/__init__.py +157 -0
- nbxtensor/tensor.py +1867 -0
- nbxtensor-0.0.1.dist-info/METADATA +379 -0
- nbxtensor-0.0.1.dist-info/RECORD +291 -0
- nbxtensor-0.0.1.dist-info/WHEEL +4 -0
- 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.
|