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,96 @@
|
|
|
1
|
+
"""Apple Metal's memory primitives (`DeviceMemory`) — the Metal device behind the one account.
|
|
2
|
+
|
|
3
|
+
The account (`memory.allocator.DeviceAllocator`) counts every byte this process holds, refuses past the device's
|
|
4
|
+
budget and reports. This object only does what the account asks of the device, over the Metal runtime
|
|
5
|
+
(`backend.metal_device.MetalRuntime`): shared-storage buffers whose one address the CPU and the GPU both use, their
|
|
6
|
+
release once the GPU is done with them, the in-place binding of host pages, copies and fills in the one address
|
|
7
|
+
space. It keeps NO account of its own: until 2026-10-29 the runtime kept a second one (`_live_bytes`) beside the
|
|
8
|
+
account's, two figures of one fact.
|
|
9
|
+
|
|
10
|
+
- `budget()`: `MTLDevice.recommendedMaxWorkingSetSize`, Apple's own ceiling for the process on unified memory. Metal
|
|
11
|
+
hands out buffers past it, backed by memory the whole machine shares, until the system pages: the ACCOUNT refuses
|
|
12
|
+
past it (`supports("budget_refusal")`), with this backend's allocation-refused status (`ALLOC_REFUSED`).
|
|
13
|
+
- `mem_get_info()`: (budget less what the account holds, budget) — `MTLDevice.currentAllocatedSize` is cumulative and
|
|
14
|
+
never comes down (measured 2026-09-05), so the headroom is read from the account, never from the device.
|
|
15
|
+
- `free(ptr)`: the buffer is released once the command buffers in flight are done with it; the account counts it
|
|
16
|
+
freed at once.
|
|
17
|
+
|
|
18
|
+
Torch-free (R33).
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import ctypes
|
|
23
|
+
from typing import Tuple
|
|
24
|
+
|
|
25
|
+
from .device_memory import DeviceMemory
|
|
26
|
+
|
|
27
|
+
#: this backend's status for an allocation refused (the runtime's `_ERR_ALLOC`): the account returns it when the budget
|
|
28
|
+
#: refuses, so a refusal reads the same whichever of the two said no
|
|
29
|
+
ALLOC_REFUSED = 1
|
|
30
|
+
|
|
31
|
+
_PRIMITIVES = frozenset({"malloc", "free", "mem_get_info", "memset", "memcpy", "host_alloc", "host_free",
|
|
32
|
+
"set_device", "get_device", "device_count", "budget", "map_host", "unmap_host",
|
|
33
|
+
"budget_refusal"})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MetalMemory(DeviceMemory):
|
|
37
|
+
"""The memory primitives of the one Metal device of this process, over its runtime."""
|
|
38
|
+
|
|
39
|
+
__slots__ = ("_rt",)
|
|
40
|
+
ALLOC_REFUSED = ALLOC_REFUSED
|
|
41
|
+
|
|
42
|
+
def __init__(self, runtime):
|
|
43
|
+
self._rt = runtime
|
|
44
|
+
|
|
45
|
+
def supports(self, primitive):
|
|
46
|
+
return primitive in _PRIMITIVES
|
|
47
|
+
|
|
48
|
+
def malloc(self, nbytes):
|
|
49
|
+
p = ctypes.c_void_p()
|
|
50
|
+
rc = self._rt.malloc(ctypes.byref(p), ctypes.c_size_t(int(nbytes)))
|
|
51
|
+
return rc, (p.value or 0)
|
|
52
|
+
|
|
53
|
+
def free(self, ptr):
|
|
54
|
+
return self._rt.free(ctypes.c_void_p(int(ptr)))
|
|
55
|
+
|
|
56
|
+
def budget(self) -> Tuple[int, int]:
|
|
57
|
+
return 0, int(self._rt.budget_bytes())
|
|
58
|
+
|
|
59
|
+
def mem_get_info(self):
|
|
60
|
+
from .allocator import DeviceAllocator as DA
|
|
61
|
+
total = int(self._rt.budget_bytes())
|
|
62
|
+
held = DA._cuda_live_bytes.get(0, 0) + DA._host_pinned_live_bytes
|
|
63
|
+
return 0, max(0, total - held), total
|
|
64
|
+
|
|
65
|
+
def memset(self, ptr, value, nbytes):
|
|
66
|
+
return self._rt.memset(int(ptr), int(value), int(nbytes))
|
|
67
|
+
|
|
68
|
+
def memcpy(self, dst, src, nbytes, kind):
|
|
69
|
+
return self._rt.memcpy(int(dst), int(src), int(nbytes), int(kind))
|
|
70
|
+
|
|
71
|
+
def host_alloc(self, nbytes):
|
|
72
|
+
p = ctypes.c_void_p()
|
|
73
|
+
rc = self._rt.malloc_host(ctypes.byref(p), ctypes.c_size_t(int(nbytes)))
|
|
74
|
+
return rc, (p.value or 0)
|
|
75
|
+
|
|
76
|
+
def host_free(self, ptr):
|
|
77
|
+
return self._rt.free_host(ctypes.c_void_p(int(ptr)))
|
|
78
|
+
|
|
79
|
+
def set_device(self, index):
|
|
80
|
+
return self._rt.set_device(int(index))
|
|
81
|
+
|
|
82
|
+
def get_device(self):
|
|
83
|
+
d = ctypes.c_int()
|
|
84
|
+
rc = self._rt.get_device(ctypes.byref(d))
|
|
85
|
+
return rc, d.value
|
|
86
|
+
|
|
87
|
+
def device_count(self):
|
|
88
|
+
n = ctypes.c_int()
|
|
89
|
+
rc = self._rt.device_count(ctypes.byref(n))
|
|
90
|
+
return rc, n.value
|
|
91
|
+
|
|
92
|
+
def map_host(self, ptr, nbytes):
|
|
93
|
+
return self._rt.bind_mapped(int(ptr), int(nbytes))
|
|
94
|
+
|
|
95
|
+
def unmap_host(self, start):
|
|
96
|
+
self._rt.unbind_mapped(start)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""What would have made it fit.
|
|
2
|
+
|
|
3
|
+
Addition 4 of `docs/reference/adaptive-memory-a-runtime-controller.md`: a refusal
|
|
4
|
+
that only says "out of memory" costs the reader the whole diagnosis. The allocator
|
|
5
|
+
knows what it asked for and what the driver had; between those two numbers there
|
|
6
|
+
is a concrete answer — how many pieces this work would have to be cut into, and
|
|
7
|
+
how much larger a card would have taken it whole.
|
|
8
|
+
|
|
9
|
+
This module says only what the numbers support. It does not guess at a remedy it
|
|
10
|
+
cannot compute: with no shape it talks about bands and card size, and it mentions
|
|
11
|
+
a spatial extent only when it is given the shape to derive one from.
|
|
12
|
+
|
|
13
|
+
It lives under `kernels/` because both execution modes raise the same
|
|
14
|
+
`DeviceOOMError` and both need the same sentence — and because `kernels/` is
|
|
15
|
+
already free of torch, so the triton branch can import it without an R33
|
|
16
|
+
question.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import math
|
|
22
|
+
from typing import Optional, Sequence
|
|
23
|
+
|
|
24
|
+
_MB = 1024 * 1024
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _mb(n: int) -> str:
|
|
28
|
+
return f"{n / _MB:,.0f} MB"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def what_would_have_fit(oom, shape: Optional[Sequence] = None) -> str:
|
|
32
|
+
"""One sentence naming what would have made the refused allocation fit.
|
|
33
|
+
|
|
34
|
+
`oom` is a `DeviceOOMError` carrying `requested` and `driver_free`. Returns
|
|
35
|
+
an empty string when it carries neither — a sentence built on a figure the
|
|
36
|
+
driver never gave would be a guess wearing the clothes of a measurement.
|
|
37
|
+
|
|
38
|
+
`shape` is the allocation's shape when the caller knows it. Its leading
|
|
39
|
+
dimension is the one a band split cuts, so it is the only one this reports
|
|
40
|
+
on; nothing here assumes NCHW or any other layout beyond "the first axis is
|
|
41
|
+
divisible", which is what band streaming actually needs.
|
|
42
|
+
"""
|
|
43
|
+
requested = getattr(oom, "requested", None)
|
|
44
|
+
free = getattr(oom, "driver_free", None)
|
|
45
|
+
if not requested or free is None:
|
|
46
|
+
return ""
|
|
47
|
+
|
|
48
|
+
parts = []
|
|
49
|
+
shortfall = getattr(oom, "shortfall", None)
|
|
50
|
+
if shortfall:
|
|
51
|
+
parts.append(f"short by {_mb(shortfall)}")
|
|
52
|
+
|
|
53
|
+
if free > 0:
|
|
54
|
+
bands = math.ceil(requested / free)
|
|
55
|
+
# `free` is what the driver has RIGHT NOW, with this run's own live set
|
|
56
|
+
# already on the card, so a band of `requested / bands` is a size that
|
|
57
|
+
# fits beside what is already there — not a hypothetical on an empty card.
|
|
58
|
+
parts.append(
|
|
59
|
+
f"it would fit in {bands} bands of about {_mb(requested / bands)}")
|
|
60
|
+
if shape:
|
|
61
|
+
lead = shape[0] if len(shape) else None
|
|
62
|
+
if isinstance(lead, int) and lead >= bands > 1:
|
|
63
|
+
parts.append(
|
|
64
|
+
f"cutting the leading axis of {tuple(shape)} from {lead} to "
|
|
65
|
+
f"{lead // bands} per band")
|
|
66
|
+
else:
|
|
67
|
+
parts.append("the card reports nothing free: no band split can help here")
|
|
68
|
+
|
|
69
|
+
total = getattr(oom, "driver_total", None)
|
|
70
|
+
if total and requested > total:
|
|
71
|
+
parts.append(
|
|
72
|
+
f"and no band split alone reaches it — the request of {_mb(requested)} "
|
|
73
|
+
f"exceeds the card's entire {_mb(total)}")
|
|
74
|
+
|
|
75
|
+
return "; ".join(parts)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def annotate(message: str, cause, shape: Optional[Sequence] = None) -> str:
|
|
79
|
+
"""Append the advice to an op-failure message when the cause is an OOM.
|
|
80
|
+
|
|
81
|
+
Any other cause passes through untouched: this must not turn an unrelated
|
|
82
|
+
failure into one that reads as a memory problem.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
from nbxtensor.memory.allocator import DeviceOOMError
|
|
86
|
+
except Exception: # pragma: no cover - import cycle safety
|
|
87
|
+
return message
|
|
88
|
+
if not isinstance(cause, DeviceOOMError):
|
|
89
|
+
return message
|
|
90
|
+
advice = what_would_have_fit(cause, shape)
|
|
91
|
+
return f"{message} | what would have fit: {advice}" if advice else message
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Device-address tables of a list of tensors: the fused MoE GEMMs read their experts through them (one [E] int64
|
|
2
|
+
table of absolute addresses per projection and device, no offset math), their fingerprinted LRU cache, and on Metal the
|
|
3
|
+
pinned scopes that keep those addresses valid for the table's whole cache life (moved from the engine's triton/moe.py,
|
|
4
|
+
M2). Torch-free.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections import defaultdict, OrderedDict
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
import triton
|
|
13
|
+
import triton.language as tl
|
|
14
|
+
|
|
15
|
+
from nbxtensor.tensor import NBXTensor
|
|
16
|
+
from nbxtensor.memory.allocator import DeviceAllocator
|
|
17
|
+
from nbxtensor.backend.runtime import gpu_backend
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# POINTER TABLE BUILDER — zero-copy, cached
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
# LRU cache of pointer tables keyed by a data_ptr fingerprint of every
|
|
25
|
+
# expert weight. Under zero3 pipelining weights may be freed and
|
|
26
|
+
# reallocated at the same virtual address, so a static cache_key
|
|
27
|
+
# (e.g. first expert's data_ptr) can resolve to a stale PtrTables and
|
|
28
|
+
# cause silent garbage or illegal memory accesses. Hashing ALL
|
|
29
|
+
# expert data_ptrs detects any change to the arena layout.
|
|
30
|
+
#
|
|
31
|
+
# Bounded by _PTR_CACHE_MAXSIZE entries — eviction is LRU (OrderedDict
|
|
32
|
+
# move_to_end on hit, popitem(last=False) on overflow). The cap keeps
|
|
33
|
+
# memory flat when running many blocks across devices during
|
|
34
|
+
# pipelining; 256 entries × 3 int64 tables × few hundred experts is
|
|
35
|
+
# still sub-megabyte.
|
|
36
|
+
_PTR_CACHE_MAXSIZE = 256
|
|
37
|
+
_ptr_cache: "OrderedDict[int, PtrTables]" = OrderedDict()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _ptr_cache_fingerprint(
|
|
41
|
+
gate_weights, up_weights, down_weights, num_experts: int
|
|
42
|
+
) -> int:
|
|
43
|
+
"""Stable hash over every expert's data_ptr for all three projections.
|
|
44
|
+
|
|
45
|
+
Any weight swap (H2D, D2D, free-and-remalloc at the same address)
|
|
46
|
+
changes at least one data_ptr — and in the rare adversarial case
|
|
47
|
+
where the driver hands out identical addresses for a pair of
|
|
48
|
+
identically-sized buffers, we still hash all 3 × num_experts ptrs
|
|
49
|
+
so a single match isn't enough for a false cache hit.
|
|
50
|
+
"""
|
|
51
|
+
vals = []
|
|
52
|
+
vals.append(num_experts)
|
|
53
|
+
for i in range(num_experts):
|
|
54
|
+
vals.append(gate_weights[i].data_ptr())
|
|
55
|
+
vals.append(up_weights[i].data_ptr())
|
|
56
|
+
vals.append(down_weights[i].data_ptr())
|
|
57
|
+
return hash(tuple(vals))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _ptr_cache_get(fp: int):
|
|
61
|
+
"""LRU lookup. Promotes the hit to the MRU end."""
|
|
62
|
+
tables = _ptr_cache.get(fp)
|
|
63
|
+
if tables is not None:
|
|
64
|
+
_ptr_cache.move_to_end(fp)
|
|
65
|
+
return tables
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _ptr_cache_put(fp: int, tables):
|
|
69
|
+
"""Insert + evict the oldest entry if we're over the cap."""
|
|
70
|
+
_ptr_cache[fp] = tables
|
|
71
|
+
_ptr_cache.move_to_end(fp)
|
|
72
|
+
while len(_ptr_cache) > _PTR_CACHE_MAXSIZE:
|
|
73
|
+
_fp, evicted = _ptr_cache.popitem(last=False)
|
|
74
|
+
evicted.release_pins()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def release_pinned_tables() -> int:
|
|
78
|
+
"""Drop every cached table that PINS (Metal), releasing its pins; returns how many. Called at the unload
|
|
79
|
+
boundary (`MemoryManager.unload_weights`), after the devices were synchronised.
|
|
80
|
+
|
|
81
|
+
A pinning table holds its expert tensors and one whole-allocation wrap per arena for as long as it lives,
|
|
82
|
+
and it lived until LRU eviction, 256 tables later: an unloaded component stayed pinned, its tensors not
|
|
83
|
+
freed and its arena still mapped. Under `layer_streaming` each unloaded segment stayed beside the next
|
|
84
|
+
(deepseek-moe-16b-chat on the Mac, 2026-10-04: swap 3.4 -> 10.2 GB in 30 s while the second of four
|
|
85
|
+
segments loaded, the guard killed the run). All of them go, not only the unloading component's: a table is
|
|
86
|
+
three small host arrays, rebuilt at the next call that misses, and a table that outlives ANY weight it
|
|
87
|
+
pins is the defect. A table that pins nothing (CUDA: integers only) is left where it is."""
|
|
88
|
+
pinned = [fp for fp, tables in _ptr_cache.items() if tables.pins]
|
|
89
|
+
for fp in pinned:
|
|
90
|
+
_ptr_cache.pop(fp).release_pins()
|
|
91
|
+
return len(pinned)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class PtrTables:
|
|
95
|
+
"""Per-projection absolute pointer tables for zero-copy expert access.
|
|
96
|
+
|
|
97
|
+
Each table is [E] int64 on GPU: absolute data_ptr() of each expert.
|
|
98
|
+
The fused kernel loads ptrs[expert_id] and uses it directly as a pointer.
|
|
99
|
+
"""
|
|
100
|
+
__slots__ = ('gate_ptrs', 'gate_stride_bk', 'gate_stride_bn',
|
|
101
|
+
'up_ptrs', 'up_stride_bk', 'up_stride_bn',
|
|
102
|
+
'down_ptrs', 'down_stride_bk', 'down_stride_bn',
|
|
103
|
+
'device_experts', 'quantized', 'q_tables', 'pins')
|
|
104
|
+
|
|
105
|
+
def __init__(self):
|
|
106
|
+
self.gate_ptrs = {} # {device → NBXTensor[E] int64}
|
|
107
|
+
self.gate_stride_bk = {}
|
|
108
|
+
self.gate_stride_bn = {}
|
|
109
|
+
self.up_ptrs = {}
|
|
110
|
+
self.up_stride_bk = {}
|
|
111
|
+
self.up_stride_bn = {}
|
|
112
|
+
self.down_ptrs = {}
|
|
113
|
+
self.down_stride_bk = {}
|
|
114
|
+
self.down_stride_bn = {}
|
|
115
|
+
self.device_experts = {}
|
|
116
|
+
# int4-g128-asym experts: *_ptrs holds the qweight table and
|
|
117
|
+
# q_tables[proj][dev] = (sc_ptrs, mn_ptrs, stride_sg, stride_sn);
|
|
118
|
+
# the *_stride_bk/bn slots hold the PACKED int32 strides.
|
|
119
|
+
self.quantized = False
|
|
120
|
+
self.q_tables = {}
|
|
121
|
+
# Metal only: entered pinned_addresses scopes, one per device — the
|
|
122
|
+
# addresses in these tables are valid EXACTLY while these are held,
|
|
123
|
+
# so their lifetime is the table's cache lifetime (released on LRU
|
|
124
|
+
# eviction below), never a call's.
|
|
125
|
+
self.pins = []
|
|
126
|
+
|
|
127
|
+
def release_pins(self):
|
|
128
|
+
for pin in self.pins:
|
|
129
|
+
try:
|
|
130
|
+
pin.__exit__(None, None, None)
|
|
131
|
+
except Exception: # noqa: BLE001
|
|
132
|
+
pass
|
|
133
|
+
self.pins = []
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@triton.jit
|
|
137
|
+
def _addr_capture_kernel(src_ptr, table_ptr, idx):
|
|
138
|
+
"""Store the DEVICE address of src_ptr into table[idx].
|
|
139
|
+
|
|
140
|
+
The bitcast is the whole point: the launcher hands the kernel the address
|
|
141
|
+
a shader dereferences, and capturing it ON DEVICE is the one spelling that
|
|
142
|
+
is correct on every backend (verified bit-equal to the driver's own
|
|
143
|
+
address on triton_ext, #130, 2026-09-19). A host-side data_ptr() is the
|
|
144
|
+
CPU mapping on Metal, and a table of those reads as silent zeros.
|
|
145
|
+
"""
|
|
146
|
+
tl.store(table_ptr + idx, tl.cast(src_ptr, tl.int64, bitcast=True))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def build_ptr_tables(gate_weights, up_weights, down_weights):
|
|
150
|
+
"""Build per-projection absolute pointer tables.
|
|
151
|
+
|
|
152
|
+
Stores each expert's data_ptr() as int64 in a GPU tensor. No offset math.
|
|
153
|
+
Total GPU allocation: 3 × E × 8 bytes per device (~3KB for 128 experts).
|
|
154
|
+
|
|
155
|
+
On Metal the tables are built by DEVICE CAPTURE under a pinned scope
|
|
156
|
+
instead: the addresses a shader dereferences, kept alive for the table's
|
|
157
|
+
whole cache lifetime (PtrTables.pins, released on LRU eviction).
|
|
158
|
+
"""
|
|
159
|
+
from nbxtensor.quant.quantized_tensor import QuantizedTensor
|
|
160
|
+
|
|
161
|
+
num_experts = len(gate_weights)
|
|
162
|
+
tables = PtrTables()
|
|
163
|
+
tables.quantized = isinstance(gate_weights[0], QuantizedTensor)
|
|
164
|
+
if tables.quantized:
|
|
165
|
+
# Uniformity contract: mixed dense/quantized expert lists are a
|
|
166
|
+
# broken build — refuse loudly (ZERO FALLBACK).
|
|
167
|
+
for proj_name, ws in (("gate", gate_weights), ("up", up_weights),
|
|
168
|
+
("down", down_weights)):
|
|
169
|
+
if not all(isinstance(x, QuantizedTensor) for x in ws):
|
|
170
|
+
raise RuntimeError(
|
|
171
|
+
f"ZERO FALLBACK: mixed dense/quantized experts in "
|
|
172
|
+
f"'{proj_name}' projection — re-build the variant.")
|
|
173
|
+
else:
|
|
174
|
+
# The kernel takes ONE (stride_bk, stride_bn) pair per projection, read
|
|
175
|
+
# from the first expert: every expert of a projection must share it. A
|
|
176
|
+
# stacked slab's views do by construction (one slab, one geometry); a
|
|
177
|
+
# list mixing layouts would be read through the wrong strides in
|
|
178
|
+
# silence — refuse it by name.
|
|
179
|
+
for proj_name, ws in (("gate", gate_weights), ("up", up_weights),
|
|
180
|
+
("down", down_weights)):
|
|
181
|
+
s0 = (tuple(ws[0].shape), ws[0].stride(0), ws[0].stride(1))
|
|
182
|
+
for eid in range(1, num_experts):
|
|
183
|
+
se = (tuple(ws[eid].shape), ws[eid].stride(0), ws[eid].stride(1))
|
|
184
|
+
if se != s0:
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
f"ZERO FALLBACK: MoE '{proj_name}' expert {eid} has "
|
|
187
|
+
f"(shape, strides) {se}, expert 0 {s0} — the grouped "
|
|
188
|
+
"GEMM reads every expert through one stride pair.")
|
|
189
|
+
|
|
190
|
+
by_device = defaultdict(list)
|
|
191
|
+
for i in range(num_experts):
|
|
192
|
+
by_device[gate_weights[i]._device_idx].append(i)
|
|
193
|
+
tables.device_experts = dict(by_device)
|
|
194
|
+
|
|
195
|
+
_metal = gpu_backend() == "metal"
|
|
196
|
+
if _metal and tables.quantized:
|
|
197
|
+
raise RuntimeError(
|
|
198
|
+
"ZERO FALLBACK: quantized (int4) expert tables are not proven on "
|
|
199
|
+
"Metal — the pinned-table contract was measured for the dense "
|
|
200
|
+
"bf16 grouped GEMM only. Adding the quantized path is proving "
|
|
201
|
+
"its triplet tables the same way, not assuming them.")
|
|
202
|
+
|
|
203
|
+
for dev, expert_ids in by_device.items():
|
|
204
|
+
DeviceAllocator.set_device(dev)
|
|
205
|
+
|
|
206
|
+
if _metal:
|
|
207
|
+
# Pin FIRST — the capture launches below store their wraps under
|
|
208
|
+
# the pin, which is what keeps the captured addresses mapped.
|
|
209
|
+
# Released when this table leaves the cache, never per call.
|
|
210
|
+
from nbxtensor.backend.triton_ext_driver import pinned_addresses
|
|
211
|
+
pin = pinned_addresses(*(
|
|
212
|
+
ws[eid]
|
|
213
|
+
for ws in (gate_weights, up_weights, down_weights)
|
|
214
|
+
for eid in expert_ids))
|
|
215
|
+
pin.__enter__()
|
|
216
|
+
tables.pins.append(pin)
|
|
217
|
+
|
|
218
|
+
# The pin's whole-allocation kept wrap is the ONE address
|
|
219
|
+
# authority: every MTLBuffer has its own GPU VA, so an address
|
|
220
|
+
# captured through a transient launch wrap dies with that wrap
|
|
221
|
+
# (zeros, nothing raised — measured 2026-09-21), and per-expert
|
|
222
|
+
# kept wraps blow Metal's alias budget (declined at ~480,
|
|
223
|
+
# granite layer 15). Host-side arithmetic on the pinned wrap's
|
|
224
|
+
# gpu_address needs no launch and one wrap per allocation.
|
|
225
|
+
from nbxtensor.backend.triton_ext_driver import pinned_gpu_address
|
|
226
|
+
|
|
227
|
+
def _capture_proj(weights):
|
|
228
|
+
ptrs = np.array(
|
|
229
|
+
[pinned_gpu_address(weights[eid].data_ptr())
|
|
230
|
+
for eid in expert_ids],
|
|
231
|
+
dtype=np.int64)
|
|
232
|
+
tab = NBXTensor.from_numpy(ptrs)
|
|
233
|
+
base = weights[expert_ids[0]]
|
|
234
|
+
return tab, base.stride(1), base.stride(0)
|
|
235
|
+
|
|
236
|
+
gp, gbk, gbn = _capture_proj(gate_weights)
|
|
237
|
+
tables.gate_ptrs[dev] = gp
|
|
238
|
+
tables.gate_stride_bk[dev] = gbk
|
|
239
|
+
tables.gate_stride_bn[dev] = gbn
|
|
240
|
+
up, ubk, ubn = _capture_proj(up_weights)
|
|
241
|
+
tables.up_ptrs[dev] = up
|
|
242
|
+
tables.up_stride_bk[dev] = ubk
|
|
243
|
+
tables.up_stride_bn[dev] = ubn
|
|
244
|
+
dp, dbk, dbn = _capture_proj(down_weights)
|
|
245
|
+
tables.down_ptrs[dev] = dp
|
|
246
|
+
tables.down_stride_bk[dev] = dbk
|
|
247
|
+
tables.down_stride_bn[dev] = dbn
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
def _build_proj(weights):
|
|
251
|
+
# Absolute pointers as int64
|
|
252
|
+
if tables.quantized:
|
|
253
|
+
qw = np.array(
|
|
254
|
+
[weights[eid].qweight.data_ptr() for eid in expert_ids],
|
|
255
|
+
dtype=np.int64)
|
|
256
|
+
sc = np.array(
|
|
257
|
+
[weights[eid].scales.data_ptr() for eid in expert_ids],
|
|
258
|
+
dtype=np.int64)
|
|
259
|
+
mn = np.array(
|
|
260
|
+
[weights[eid].qmins.data_ptr() for eid in expert_ids],
|
|
261
|
+
dtype=np.int64)
|
|
262
|
+
base = weights[expert_ids[0]]
|
|
263
|
+
return ((NBXTensor.from_numpy(qw),
|
|
264
|
+
NBXTensor.from_numpy(sc),
|
|
265
|
+
NBXTensor.from_numpy(mn)),
|
|
266
|
+
base.qweight.stride(0), base.qweight.stride(1),
|
|
267
|
+
base.scales.stride(0), base.scales.stride(1))
|
|
268
|
+
ptrs = np.array(
|
|
269
|
+
[weights[eid].data_ptr() for eid in expert_ids],
|
|
270
|
+
dtype=np.int64)
|
|
271
|
+
ptr_table = NBXTensor.from_numpy(ptrs)
|
|
272
|
+
base = weights[expert_ids[0]]
|
|
273
|
+
return ptr_table, base.stride(1), base.stride(0)
|
|
274
|
+
|
|
275
|
+
if tables.quantized:
|
|
276
|
+
for name, ws in (("gate", gate_weights), ("up", up_weights),
|
|
277
|
+
("down", down_weights)):
|
|
278
|
+
(qw_t, sc_t, mn_t), qk, qn, sg, sn = _build_proj(ws)
|
|
279
|
+
getattr(tables, f"{name}_ptrs")[dev] = qw_t
|
|
280
|
+
getattr(tables, f"{name}_stride_bk")[dev] = qk
|
|
281
|
+
getattr(tables, f"{name}_stride_bn")[dev] = qn
|
|
282
|
+
tables.q_tables.setdefault(name, {})[dev] = (
|
|
283
|
+
sc_t, mn_t, sg, sn)
|
|
284
|
+
continue
|
|
285
|
+
|
|
286
|
+
gp, gbk, gbn = _build_proj(gate_weights)
|
|
287
|
+
tables.gate_ptrs[dev] = gp
|
|
288
|
+
tables.gate_stride_bk[dev] = gbk
|
|
289
|
+
tables.gate_stride_bn[dev] = gbn
|
|
290
|
+
|
|
291
|
+
up, ubk, ubn = _build_proj(up_weights)
|
|
292
|
+
tables.up_ptrs[dev] = up
|
|
293
|
+
tables.up_stride_bk[dev] = ubk
|
|
294
|
+
tables.up_stride_bn[dev] = ubn
|
|
295
|
+
|
|
296
|
+
dp, dbk, dbn = _build_proj(down_weights)
|
|
297
|
+
tables.down_ptrs[dev] = dp
|
|
298
|
+
tables.down_stride_bk[dev] = dbk
|
|
299
|
+
tables.down_stride_bn[dev] = dbn
|
|
300
|
+
|
|
301
|
+
return tables
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _tables_for_call(gate_weights, up_weights, down_weights, num_experts: int,
|
|
305
|
+
per_call: bool, act_dev: int):
|
|
306
|
+
"""The pointer tables of one call. Weights that live for this call only
|
|
307
|
+
(promoted from the host here or by the dispatcher) get a fresh table that
|
|
308
|
+
never enters the cache — their addresses are freed when the call returns,
|
|
309
|
+
and a later allocation reusing them would meet a stale entry. Resident
|
|
310
|
+
weights go through the fingerprinted LRU cache."""
|
|
311
|
+
if per_call:
|
|
312
|
+
tables = build_ptr_tables(gate_weights, up_weights, down_weights)
|
|
313
|
+
DeviceAllocator.set_device(act_dev)
|
|
314
|
+
DeviceAllocator.ensure_triton_device(act_dev)
|
|
315
|
+
return tables
|
|
316
|
+
fp = _ptr_cache_fingerprint(gate_weights, up_weights, down_weights, num_experts)
|
|
317
|
+
tables = _ptr_cache_get(fp)
|
|
318
|
+
if tables is None:
|
|
319
|
+
tables = build_ptr_tables(gate_weights, up_weights, down_weights)
|
|
320
|
+
_ptr_cache_put(fp, tables)
|
|
321
|
+
DeviceAllocator.set_device(act_dev)
|
|
322
|
+
DeviceAllocator.ensure_triton_device(act_dev)
|
|
323
|
+
return tables
|