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,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)
|