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,409 @@
1
+ """The device runtime in force: which backend this process detected (CUDA, HIP, Metal), its runtime library, the
2
+ current device, and the Triton backend pinned to it. Cut out of tensor.py (M2, 2026-10-29), unchanged."""
3
+
4
+ from __future__ import annotations
5
+ from nbxtensor.memory import allocator as _alloc
6
+
7
+ import ctypes
8
+ import functools
9
+ import os
10
+ import threading
11
+ from typing import Optional
12
+
13
+ # module level, not in device_memory(): it runs on the finalizer path, where an import at shutdown raises
14
+ from nbxtensor.memory.device_memory import AbiMemory as _AbiMemory
15
+
16
+
17
+
18
+ # ============================================================================
19
+ # GPU RUNTIME — hardware-agnostic via ctypes (CUDA/ROCm)
20
+ # ============================================================================
21
+
22
+ def _environment_runtime_libs(package: str, names) -> list:
23
+ """The runtime libraries the ENVIRONMENT ships, ahead of the system's.
24
+ torch's CUDA wheels carry their own `nvidia/cuda_runtime/lib/libcudart.so.12`
25
+ and bind against it; a `libcudart.so.12` resolved by ldconfig is the
26
+ machine's toolkit (12.2 on this rack), and when the engine's ctypes loader
27
+ opened THAT one first, torch 2.14's `libc10_cuda.so` found an older runtime
28
+ than it was built on — `undefined symbol: cudaGetDriverEntryPointByVersion`
29
+ (2026-09-16, the stack alignment). One process, one runtime: the wheel's
30
+ own when it exists, the system's otherwise. Read from the package's
31
+ location, never a typed path."""
32
+ try:
33
+ import importlib.util
34
+ spec = importlib.util.find_spec(package)
35
+ except (ImportError, ValueError):
36
+ spec = None
37
+ if spec is None or not spec.submodule_search_locations:
38
+ return []
39
+ import os
40
+ root = list(spec.submodule_search_locations)[0]
41
+ out = []
42
+ for n in names:
43
+ cand = os.path.join(root, "lib", n)
44
+ if os.path.exists(cand):
45
+ out.append(cand)
46
+ return out
47
+
48
+
49
+ # Runtime API mapping per backend
50
+ _GPU_BACKENDS = {
51
+ "cuda": {
52
+ "rt_libs": _environment_runtime_libs("nvidia.cuda_runtime", ["libcudart.so.12"])
53
+ + ["libcudart.so", "libcudart.so.12", "libcudart.so.11.0"],
54
+ # the driver API's library: the virtual memory primitives of the expandable pool (`memory.vmm`, R16)
55
+ "driver_libs": ["libcuda.so.1", "libcuda.so"],
56
+ # the management library: what the driver attributes to THIS process on a device (`backend.process_memory`)
57
+ "management_libs": ["libnvidia-ml.so.1", "libnvidia-ml.so"],
58
+ "pci_bus_id": "cudaDeviceGetPCIBusId",
59
+ "malloc": "cudaMalloc", "free": "cudaFree",
60
+ "memcpy": "cudaMemcpy", "memset": "cudaMemset",
61
+ "set_device": "cudaSetDevice",
62
+ "get_last_error": "cudaGetLastError", "get_device": "cudaGetDevice",
63
+ "device_count": "cudaGetDeviceCount",
64
+ "mem_get_info": "cudaMemGetInfo",
65
+ "sync": "cudaDeviceSynchronize",
66
+ "malloc_host": "cudaMallocHost", "free_host": "cudaFreeHost",
67
+ "host_register": "cudaHostRegister", "host_unregister": "cudaHostUnregister",
68
+ # Stream-ordered memory allocator (CUDA 11.2+). The driver
69
+ # handles kernel-lifetime correctness per-stream, so freeing a
70
+ # tensor while an async kernel is still reading from it is
71
+ # safe — the free is ordered against the stream's pending work.
72
+ # Using stream=0 (default stream) keeps us aligned with the
73
+ # rest of NeuroBrix's single-default-stream execution model;
74
+ # when pipelining introduces a transfer stream in a future
75
+ # session we'll route those allocs to it.
76
+ "malloc_async": "cudaMallocAsync", "free_async": "cudaFreeAsync",
77
+ # Stream + event API — used by zero3 pipelining for async H2D
78
+ # overlap of block N+1 with compute on block N.
79
+ "stream_create": "cudaStreamCreate",
80
+ "stream_destroy": "cudaStreamDestroy",
81
+ "stream_sync": "cudaStreamSynchronize",
82
+ "event_create": "cudaEventCreate",
83
+ "event_create_flags": "cudaEventCreateWithFlags",
84
+ "event_destroy": "cudaEventDestroy",
85
+ "event_record": "cudaEventRecord",
86
+ "stream_wait_event": "cudaStreamWaitEvent",
87
+ "memcpy_async": "cudaMemcpyAsync",
88
+ # Event timing — the ONLY way to measure a device-side interval
89
+ # without a host round-trip. `event_elapsed` is undefined across
90
+ # devices, so both events of a pair must be recorded on streams of
91
+ # the SAME device (see DeviceAllocator.event_elapsed_ms).
92
+ "event_sync": "cudaEventSynchronize",
93
+ "event_elapsed": "cudaEventElapsedTime",
94
+ # Peer access. Without it a cross-device D2D copy is STAGED THROUGH
95
+ # HOST MEMORY by the driver — it never touches NVLink. Enabling it
96
+ # is per ORDERED pair and per process.
97
+ "device_can_access_peer": "cudaDeviceCanAccessPeer",
98
+ "device_enable_peer_access": "cudaDeviceEnablePeerAccess",
99
+ },
100
+ "hip": {
101
+ "rt_libs": ["libamdhip64.so", "libamdhip64.so.5"],
102
+ # the same three facts as cuda's, declared ABSENT: the expandable pool speaks the CUDA driver's VMM calls only
103
+ # (`memory.vmm`; HIP's hipMemCreate family is not wired), so a HIP device keeps its driver segments; and no
104
+ # per-process accounting is read on ROCm (`backend.process_memory` answers None, the runtime's growth is not
105
+ # measured there, said) — never a CUDA library tried on an AMD card.
106
+ "driver_libs": [],
107
+ "management_libs": [],
108
+ "pci_bus_id": "hipDeviceGetPCIBusId",
109
+ "malloc": "hipMalloc", "free": "hipFree",
110
+ "memcpy": "hipMemcpy", "memset": "hipMemset",
111
+ "set_device": "hipSetDevice",
112
+ "get_last_error": "hipGetLastError", "get_device": "hipGetDevice",
113
+ "device_count": "hipGetDeviceCount",
114
+ "mem_get_info": "hipMemGetInfo",
115
+ "sync": "hipDeviceSynchronize",
116
+ "malloc_host": "hipHostMalloc", "free_host": "hipHostFree",
117
+ "host_register": "hipHostRegister", "host_unregister": "hipHostUnregister",
118
+ # ROCm 4.3+ exposes the same stream-ordered API under hip*
119
+ # names. Not tested in this pass (investigation was V100-only),
120
+ # but the symbol mapping is kept for symmetry.
121
+ "malloc_async": "hipMallocAsync", "free_async": "hipFreeAsync",
122
+ "stream_create": "hipStreamCreate",
123
+ "stream_destroy": "hipStreamDestroy",
124
+ "stream_sync": "hipStreamSynchronize",
125
+ "event_create": "hipEventCreate",
126
+ "event_create_flags": "hipEventCreateWithFlags",
127
+ "event_destroy": "hipEventDestroy",
128
+ "event_record": "hipEventRecord",
129
+ "stream_wait_event": "hipStreamWaitEvent",
130
+ "memcpy_async": "hipMemcpyAsync",
131
+ "event_sync": "hipEventSynchronize",
132
+ "event_elapsed": "hipEventElapsedTime",
133
+ # ROCm exposes peer access under the same semantics; xGMI plays the
134
+ # role NVLink plays here.
135
+ "device_can_access_peer": "hipDeviceCanAccessPeer",
136
+ "device_enable_peer_access": "hipDeviceEnablePeerAccess",
137
+ },
138
+ }
139
+
140
+
141
+ # ============================================================================
142
+ # CURRENT-DEVICE CACHE (P-KERNEL-LAUNCH-HYGIENE C2)
143
+ # ============================================================================
144
+ #
145
+ # `ensure_triton_device` is called before every Triton kernel launch
146
+ # (via `set_device`). The cudaSetDevice + triton set_current_device
147
+ # pair is pure overhead when the device is already current, so we cache
148
+ # the last device index that was fully synchronised (runtime device AND
149
+ # Triton driver device both pointing at it) and skip the pair on a hit.
150
+ #
151
+ # Cache invariant: `_cached_device_idx() == idx` (per-thread) implies the CUDA/HIP
152
+ # runtime current device is `idx` AND Triton's active driver device is
153
+ # `idx`. Every NeuroBrix-side device change maintains the invariant:
154
+ # - `ensure_triton_device` refreshes the cache after a full sync;
155
+ # - `DeviceAllocator.set_device` (the single choke point for runtime-
156
+ # only switches) invalidates the cache when it switches to a device
157
+ # that differs from the cached one (it does not touch Triton's
158
+ # driver device, so the pair is no longer known-coherent);
159
+ # - `most_free_device` (raw per-device probe loop) invalidates
160
+ # unconditionally before returning.
161
+ # Device changes that bypass NBX entirely (torch.cuda.set_device on the
162
+ # compiled-mode path) cannot interleave with a warm cache inside a
163
+ # triton run (R33: no torch in triton paths); triton run entry points
164
+ # invalidate the cache defensively (see triton/sequence.py run()).
165
+ #
166
+ # THREAD-LOCAL, not process-global (D9 root cause, 2026-07-27): the
167
+ # cudaSetDevice/hipSetDevice state the invariant mirrors IS per-thread.
168
+ # A process-global cache warmed by one thread makes every OTHER thread
169
+ # skip the sync while its own runtime device is still the default —
170
+ # the serving daemon's worker thread then launches kernels holding
171
+ # cuda:N pointers from a device-0 context ("CUDA: out of memory" at
172
+ # launch with gigabytes free on every device; cmd_run, single-threaded,
173
+ # can never reproduce it).
174
+ _DEVICE_CACHE = threading.local()
175
+
176
+
177
+ def _cached_device_idx() -> Optional[int]:
178
+ return getattr(_DEVICE_CACHE, "idx", None)
179
+
180
+
181
+ def invalidate_current_device_cache() -> None:
182
+ """Drop the ensure_triton_device fast-path cache (this thread).
183
+
184
+ Call after any device switch that does not go through
185
+ `DeviceAllocator.set_device` / `ensure_triton_device`, so the next
186
+ `ensure_triton_device` performs the full runtime + Triton sync.
187
+ """
188
+ _DEVICE_CACHE.idx = None
189
+
190
+
191
+ def set_device(t):
192
+ """Sync Triton/CUDA driver context with the tensor's physical device.
193
+
194
+ Called before every Triton kernel launch. Triton's internal driver
195
+ state must match `t._device_idx`, or the kernel will target the
196
+ wrong device and raise "Pointer argument cannot be accessed from
197
+ Triton" when the pointer lives on a different GPU. Single source of
198
+ truth — imported by wrappers.py, dispatch.py, shape_utils.py,
199
+ triton/weight_loader.py so every kernel-launching module honours
200
+ the contract.
201
+ """
202
+ if hasattr(t, '_device_idx'):
203
+ _alloc.DeviceAllocator.ensure_triton_device(t._device_idx)
204
+
205
+
206
+ #: This engine names its backends for the vendor RUNTIME it loads (`cuda`, `hip`,
207
+ #: `metal`); Triton names its own for the vendor's COMPILER target, which is the
208
+ #: directory under `triton/backends/`. The two vocabularies are not the same and
209
+ #: the mapping between them belongs here, once.
210
+ _TRITON_BACKEND_BY_RUNTIME = {"cuda": "nvidia", "hip": "amd", "metal": "metal"}
211
+
212
+
213
+ def _pin_triton_backend(name: str) -> str:
214
+ """Tell Triton which backend is here, then return the name.
215
+
216
+ R33, and it applies on every backend, not just Apple. Asking
217
+ `triton.runtime.driver.active` for anything makes Triton call
218
+ `is_active()` on EVERY registered backend to find the live one, and
219
+ upstream's AMD probe runs `import torch` inside its own — so a CUDA box
220
+ with no AMD card pays a torch import the moment any module asks the
221
+ driver a question. `kernels/ops/matmul.py` asks one at import time, which
222
+ is how it reached us.
223
+
224
+ `TRITON_DEFAULT_BACKEND` makes `_create_driver` select the named backend
225
+ directly and probe nothing else. We already know the answer here — this
226
+ function found it without importing anything — so we say so. `setdefault`,
227
+ because an explicit choice by the user or a test outranks ours.
228
+
229
+ **The name has to be TRITON'S, and ours is not.** This engine's backends are
230
+ called `cuda`, `hip` and `metal`; Triton's registry keys are the directory
231
+ names under `triton/backends/` — `nvidia` and `amd` — and `_create_driver`
232
+ raises `Unknown backend device '<name>'` on anything else. Pinning `cuda` was
233
+ therefore always wrong and never showed, because this engine's launcher does
234
+ every launch itself and never asks Triton for a driver. It showed the moment
235
+ a launch went down Triton's own path: torch 2.14 ships in-tree Triton ops and
236
+ the seam now hands those back to Triton, which then read the pin (2026-09-17).
237
+
238
+ A name Triton does not have is worse than no pin at all, so the mapped name is
239
+ checked against Triton's own registry when that can be read without probing —
240
+ `triton.backends` is a dict built at import and `is_active()` runs only inside
241
+ `_create_driver` — and the pin is skipped rather than set to something that
242
+ raises.
243
+ """
244
+ triton_name = _TRITON_BACKEND_BY_RUNTIME.get(name, name)
245
+ try:
246
+ from triton.backends import backends as _triton_backends
247
+ if triton_name not in _triton_backends:
248
+ return name # do not pin a name Triton would refuse
249
+ except Exception: # noqa: BLE001 — no Triton, or a shape we do not know
250
+ pass
251
+ os.environ.setdefault("TRITON_DEFAULT_BACKEND", triton_name)
252
+ return name
253
+
254
+
255
+ @functools.lru_cache(maxsize=1)
256
+ def gpu_backend() -> str:
257
+ """Detect GPU backend: 'cuda', 'hip' or 'metal' — from the vendor runtime
258
+ the process can load, or `NBX_GPU_BACKEND`.
259
+
260
+ **What this does NOT answer: whether a device is there.** On CUDA and ROCm
261
+ it succeeds when the vendor's runtime LIBRARY loads, which it does on a host
262
+ with the toolkit and no visible card — it answered "cuda" under
263
+ `CUDA_VISIBLE_DEVICES=` and five tests that had gated on it went on to fail
264
+ at their first allocation with `cudaErrorNoDevice` instead of skipping
265
+ (2026-09-16, register 62). On Metal the two questions coincide, because
266
+ there is no library to dlopen and the probe opens the device itself — which
267
+ is why the same call is an executing probe on one backend and a naming one
268
+ on the others. A caller asking "can I run here" wants
269
+ `DeviceAllocator.device_count() > 0`, which asks the driver, or an
270
+ allocation, which answers for the memory too.
271
+
272
+ Never through Triton's driver
273
+ probe: `triton.runtime.driver.active` asks every backend `is_active()`,
274
+ and those probes import torch (R33, universal since 2026-09-05 — this
275
+ call was the first torch import of the launch path).
276
+
277
+ **Every probe here is torch-free, and the order is R33 rather than
278
+ taste.** Asking Triton which backend is active — the obvious first
279
+ question, and what this function used to do — makes Triton call
280
+ `is_active()` on EVERY registered backend to find the live one, and
281
+ upstream's AMD probe runs `import torch` inside its own. So merely asking
282
+ puts torch in the process, on a CUDA box with no AMD card as much as on a
283
+ Mac. `kernels/ops/matmul.py` asks that question at module import, which is
284
+ how it reached the engine.
285
+
286
+ The Metal probe carries no platform strings. It opens the device and asks
287
+ it, because the property that matters is unified memory (which is what
288
+ makes one address valid for both processors) and that is a device answer,
289
+ not an `arm64` answer. It has no `rt_libs` row to try, which is why it is
290
+ not an entry in `_GPU_BACKENDS`: that dict holds symbol tables over one C
291
+ ABI and `test_device_backend_seam.py` pins it to exactly {cuda, hip}.
292
+
293
+ Whatever answers, `_pin_triton_backend` records it in
294
+ `TRITON_DEFAULT_BACKEND`, so that the differential arm
295
+ (`NBX_LAUNCHER=triton`) — the only path that still reaches
296
+ `triton.runtime.driver.active` — resolves straight to that backend and
297
+ probes nothing else.
298
+ """
299
+ forced = os.environ.get("NBX_GPU_BACKEND")
300
+ if forced:
301
+ if forced not in _GPU_BACKENDS and forced != "metal":
302
+ raise RuntimeError(f"NBX_GPU_BACKEND={forced!r} is not a known backend "
303
+ f"({sorted(_GPU_BACKENDS) + ['metal']})")
304
+ return _pin_triton_backend(forced)
305
+ # The backend TABLE decides: the first vendor runtime the process can
306
+ # load names the backend — adding a backend is adding a table entry.
307
+ for name, backend in _GPU_BACKENDS.items():
308
+ for lib in backend["rt_libs"]:
309
+ try:
310
+ ctypes.cdll.LoadLibrary(lib)
311
+ return _pin_triton_backend(name)
312
+ except OSError:
313
+ continue
314
+ # Apple GPUs. Not a "fallback" in the sense the loop above is: there is
315
+ # no library to dlopen, so the probe opens the device itself and answers
316
+ # only when a usable one came back.
317
+ from nbxtensor.backend.metal_device import metal_device_available
318
+ if metal_device_available():
319
+ return _pin_triton_backend("metal")
320
+ raise RuntimeError("No GPU runtime found (tried CUDA, ROCm/HIP and Metal)")
321
+
322
+
323
+ def device_label(tensor) -> str:
324
+ """How a tensor's placement should READ in a message, e.g. "metal:0".
325
+
326
+ `NBXTensor._device` is the engine's internal token for "device memory"
327
+ and its value is the string `"cuda"` on every backend — `is_cuda()` and
328
+ the allocator paths are written against that token, so it is not a name
329
+ to change. It is also not a name to PRINT: on an Apple machine a refusal
330
+ that says `arg0=cuda:0` is telling the reader something false about the
331
+ hardware, and the first Metal diagnostics did exactly that.
332
+
333
+ So the token stays and the label is resolved, once, from the backend the
334
+ engine actually detected. A tensor on the host still reads "cpu", which
335
+ is true everywhere.
336
+ """
337
+ placement = getattr(tensor, "_device", None)
338
+ index = getattr(tensor, "_device_idx", "?")
339
+ if placement is None:
340
+ return "?"
341
+ if placement != "cuda":
342
+ return f"{placement}:{index}"
343
+ try:
344
+ return f"{gpu_backend()}:{index}"
345
+ except Exception:
346
+ # No backend resolvable: say the token rather than invent a name.
347
+ return f"{placement}:{index}"
348
+
349
+
350
+ def backend_table() -> dict:
351
+ """The entry-point name table for the detected backend.
352
+
353
+ Metal resolves to a table that is deliberately NOT a row in
354
+ `_GPU_BACKENDS`. That dict holds symbol tables over one C ABI, and
355
+ `test_device_backend_seam.py` pins it to exactly `{"cuda", "hip"}`
356
+ because a key in one row and missing from the other is a crash on that
357
+ hardware alone. Metal is a different API with a different set of honest
358
+ entry points, so it gets its own table — the second *implementation*
359
+ behind this seam that the seam's own docstring describes.
360
+ """
361
+ name = gpu_backend()
362
+ if name == "metal":
363
+ return _metal_backend_table()
364
+ return _GPU_BACKENDS[name]
365
+
366
+
367
+ @functools.lru_cache(maxsize=1)
368
+ def _metal_backend_table() -> dict:
369
+ """The Metal entry-point table, imported ONCE and then cached.
370
+
371
+ Cached rather than imported per call because `backend_table` runs
372
+ inside `NBXTensor.__del__`, and a finalizer can fire during interpreter
373
+ shutdown when `sys.meta_path` is already None — an import there raises
374
+ `ImportError: Python is likely shutting down` and every tensor still
375
+ alive prints a traceback on the way out. The cuda/hip path never had the
376
+ problem: `_GPU_BACKENDS` is a module global with no import behind it.
377
+ One `lru_cache` gives Metal the same property.
378
+ """
379
+ from nbxtensor.backend.metal_device import METAL_BACKEND
380
+ return METAL_BACKEND
381
+
382
+
383
+ @functools.lru_cache(maxsize=1)
384
+ def gpu_runtime():
385
+ """The runtime of the GPU backend in force: the Metal runtime object on Metal, else the vendor's runtime library
386
+ (CUDA, HIP) loaded through its C ABI, the first of the backend table's libraries that loads."""
387
+ name = gpu_backend()
388
+ if name == "metal":
389
+ from nbxtensor.backend.metal_device import runtime
390
+ return runtime() # itself cached; see _metal_backend_table
391
+ backend = backend_table()
392
+ for lib in backend["rt_libs"]:
393
+ try:
394
+ return ctypes.cdll.LoadLibrary(lib)
395
+ except OSError:
396
+ continue
397
+ raise RuntimeError(f"GPU runtime not found for {name}")
398
+
399
+
400
+ def device_memory():
401
+ """The memory primitives of the backend in force (`memory.device_memory.DeviceMemory`): the account
402
+ (`memory.allocator.DeviceAllocator`) asks these and nothing else of the device's memory. Built per call from the
403
+ runtime and its table, never cached: a substituted runtime or table is read at once."""
404
+ table = backend_table()
405
+ if gpu_backend() == "metal" and table is _metal_backend_table():
406
+ # Metal's own primitives, no C ABI behind them; a substituted table (a test's C-ABI driver) is read as one
407
+ from nbxtensor.memory.metal import MetalMemory
408
+ return MetalMemory(gpu_runtime())
409
+ return _AbiMemory(gpu_runtime(), table)