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,20 @@
|
|
|
1
|
+
"""Zeros — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Fills a tensor with zeros. Specialization of fill.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import triton
|
|
7
|
+
import triton.language as tl
|
|
8
|
+
|
|
9
|
+
@triton.jit
|
|
10
|
+
def zeros_kernel(
|
|
11
|
+
output_ptr,
|
|
12
|
+
n_elements,
|
|
13
|
+
BLOCK_SIZE: tl.constexpr,
|
|
14
|
+
):
|
|
15
|
+
"""Fill output with zeros."""
|
|
16
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
17
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
18
|
+
mask = offset < n_elements
|
|
19
|
+
zero = tl.zeros([BLOCK_SIZE], dtype=output_ptr.type.element_ty)
|
|
20
|
+
tl.store(output_ptr + offset, zero, mask=mask)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Quantized tensors."""
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""QuantizedTensor — the engine-side handle for an encoded weight.
|
|
2
|
+
|
|
3
|
+
A weight stored under the int4-g128-asym encoding rides the container
|
|
4
|
+
as a triplet (`X.qweight` int32 [K//8, N], `X.scales` / `X.qmins`
|
|
5
|
+
fp16 [K//group, N], K = in_features). At weight-load the triplet is
|
|
6
|
+
assembled into ONE QuantizedTensor registered under the graph's
|
|
7
|
+
expected key (`X.weight`), so binding, placement accounting and
|
|
8
|
+
lifecycle stay untouched — compute wrappers detect the type and route:
|
|
9
|
+
|
|
10
|
+
aten::t -> returns self with the view flag flipped. The packed
|
|
11
|
+
layout is ALREADY the transposed orientation [K, N]
|
|
12
|
+
(the dequant-GEMV family's natural layout), so the
|
|
13
|
+
graph's `t(weight [N, K])` is a zero-cost marker.
|
|
14
|
+
aten::mm -> M == 1: fused dequant-GEMV (byte-gated family);
|
|
15
|
+
M > 1: dequantize to dense fp32 via the family's
|
|
16
|
+
standalone kernel, then the normal mm path (prefill is
|
|
17
|
+
compute-bound; the transient dense buffer frees at op
|
|
18
|
+
end).
|
|
19
|
+
|
|
20
|
+
Lives in kernels/ beside NBXTensor (wrappers route on this type; triton/ consumes it). R33: NBXTensor members only, zero torch. The logical metadata
|
|
21
|
+
(shape/dtype) mirrors what the dense weight would expose so generic
|
|
22
|
+
binding/accounting code needs no special cases.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Tuple
|
|
26
|
+
|
|
27
|
+
from nbxtensor.tensor import NBXTensor
|
|
28
|
+
from nbxtensor.dtype.nbx_dtype import NBXDtype
|
|
29
|
+
|
|
30
|
+
ENCODING = "int4-g128-asym"
|
|
31
|
+
GROUP = 128
|
|
32
|
+
# The leaves an encoded `X.weight` is stored under, in fold order
|
|
33
|
+
# (packed values, per-group scales, per-group minimums).
|
|
34
|
+
STORAGE_LEAVES = ("qweight", "scales", "qmins")
|
|
35
|
+
PACK = 8
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class QuantizedTensor:
|
|
39
|
+
"""int4-g128-asym weight triplet under one graph-facing handle.
|
|
40
|
+
|
|
41
|
+
`logical_shape` is the DENSE weight's shape as the graph traced it
|
|
42
|
+
([out_features, in_features]); `transposed=True` marks the
|
|
43
|
+
graph-side aten::t view ([in, out] — the packed layout's natural
|
|
44
|
+
orientation)."""
|
|
45
|
+
|
|
46
|
+
__slots__ = ("qweight", "scales", "qmins", "logical_shape",
|
|
47
|
+
"transposed")
|
|
48
|
+
|
|
49
|
+
def __init__(self, qweight: NBXTensor, scales: NBXTensor,
|
|
50
|
+
qmins: NBXTensor, logical_shape: Tuple[int, int],
|
|
51
|
+
transposed: bool = False):
|
|
52
|
+
self.qweight = qweight
|
|
53
|
+
self.scales = scales
|
|
54
|
+
self.qmins = qmins
|
|
55
|
+
self.logical_shape = tuple(logical_shape)
|
|
56
|
+
self.transposed = transposed
|
|
57
|
+
|
|
58
|
+
# ── graph-facing metadata (mirrors the dense weight) ──
|
|
59
|
+
@property
|
|
60
|
+
def shape(self) -> Tuple[int, int]:
|
|
61
|
+
n, k = self.logical_shape
|
|
62
|
+
return (k, n) if self.transposed else (n, k)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def ndim(self) -> int:
|
|
66
|
+
return 2
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def dtype(self):
|
|
70
|
+
return NBXDtype.float16 # the dense weight's traced dtype class
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def _dtype(self):
|
|
74
|
+
return NBXDtype.float16
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def _shape(self) -> Tuple[int, int]:
|
|
78
|
+
return self.shape
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def nbx_dtype(self):
|
|
82
|
+
return NBXDtype.float16
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def device(self):
|
|
86
|
+
return self.qweight.device
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def _device(self):
|
|
90
|
+
return self.qweight._device
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def _device_idx(self):
|
|
94
|
+
return self.qweight._device_idx
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def _nbytes(self) -> int:
|
|
98
|
+
return (self.qweight._nbytes + self.scales._nbytes
|
|
99
|
+
+ self.qmins._nbytes)
|
|
100
|
+
|
|
101
|
+
def numel(self) -> int:
|
|
102
|
+
n, k = self.logical_shape
|
|
103
|
+
return n * k
|
|
104
|
+
|
|
105
|
+
def data_ptr(self) -> int:
|
|
106
|
+
"""Identity pointer for cache fingerprints (the qweight buffer
|
|
107
|
+
— stable for the triplet's lifetime)."""
|
|
108
|
+
return self.qweight.data_ptr()
|
|
109
|
+
|
|
110
|
+
def pin_host(self) -> "QuantizedTensor":
|
|
111
|
+
"""The three host parts pinned (zero3 offload of an int4 weight)."""
|
|
112
|
+
return QuantizedTensor(self.qweight.pin_host(), self.scales.pin_host(), self.qmins.pin_host(),
|
|
113
|
+
self.logical_shape, self.transposed)
|
|
114
|
+
|
|
115
|
+
# ── the movers: a weight is moved by ASKING THE WEIGHT ──
|
|
116
|
+
#
|
|
117
|
+
# Every placement path in the engine moves a weight by calling a mover on
|
|
118
|
+
# the tensor itself (`strategies/base.py`, `strategies/zero3.py`,
|
|
119
|
+
# `strategies/triton/base.py`): dense weights answer, and an encoded one
|
|
120
|
+
# that cannot answer breaks the first time a plan streams or offloads.
|
|
121
|
+
# Measured 2026-09-16 on the vitrine's first LLM request:
|
|
122
|
+
# Qwen3-Coder-30B-A3B-Instruct-int4g128 died at
|
|
123
|
+
# `'QuantizedTensor' object has no attribute 'to_cuda_async'` — the plan
|
|
124
|
+
# was right, the encoding was right, the handle was missing three methods.
|
|
125
|
+
# The triplet moves as a triplet; the metadata (logical shape, view flag)
|
|
126
|
+
# is not a property of where the bytes live, so it rides unchanged.
|
|
127
|
+
|
|
128
|
+
def to_cuda(self, device_idx: int = 0) -> "QuantizedTensor":
|
|
129
|
+
"""The three parts on the given card, the triplet rebuilt."""
|
|
130
|
+
return QuantizedTensor(self.qweight.to_cuda(device_idx),
|
|
131
|
+
self.scales.to_cuda(device_idx),
|
|
132
|
+
self.qmins.to_cuda(device_idx),
|
|
133
|
+
self.logical_shape, self.transposed)
|
|
134
|
+
|
|
135
|
+
def to_cuda_async(self, device_idx: int = 0, stream: int = 0) -> "QuantizedTensor":
|
|
136
|
+
"""The three parts enqueued on `stream` (the streaming strategies'
|
|
137
|
+
mover). Sequencing is the caller's, exactly as for a dense weight:
|
|
138
|
+
the returned triplet is readable once the stream is waited on."""
|
|
139
|
+
return QuantizedTensor(self.qweight.to_cuda_async(device_idx, stream=stream),
|
|
140
|
+
self.scales.to_cuda_async(device_idx, stream=stream),
|
|
141
|
+
self.qmins.to_cuda_async(device_idx, stream=stream),
|
|
142
|
+
self.logical_shape, self.transposed)
|
|
143
|
+
|
|
144
|
+
def to_cpu(self, pinned: bool = False) -> "QuantizedTensor":
|
|
145
|
+
"""The three parts back on the host (offload), pinned if asked."""
|
|
146
|
+
return QuantizedTensor(self.qweight.to_cpu(pinned=pinned),
|
|
147
|
+
self.scales.to_cpu(pinned=pinned),
|
|
148
|
+
self.qmins.to_cpu(pinned=pinned),
|
|
149
|
+
self.logical_shape, self.transposed)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def _pinned(self) -> bool:
|
|
153
|
+
return bool(getattr(self.qweight, "_pinned", False))
|
|
154
|
+
|
|
155
|
+
def t(self) -> "QuantizedTensor":
|
|
156
|
+
return QuantizedTensor(self.qweight, self.scales, self.qmins,
|
|
157
|
+
self.logical_shape,
|
|
158
|
+
transposed=not self.transposed)
|
|
159
|
+
|
|
160
|
+
def __repr__(self) -> str:
|
|
161
|
+
return (f"QuantizedTensor({ENCODING}, logical={self.logical_shape}, "
|
|
162
|
+
f"transposed={self.transposed}, "
|
|
163
|
+
f"packed={tuple(self.qweight.shape)})")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def assemble_quantized(weights: dict) -> int:
|
|
167
|
+
"""Fold `.qweight`/`.scales`/`.qmins` triplets in a loaded weights
|
|
168
|
+
dict into QuantizedTensor entries under the graph key `<base>.weight`.
|
|
169
|
+
Mutates the dict in place; returns the number of assembled weights.
|
|
170
|
+
ZERO FALLBACK: an incomplete triplet raises."""
|
|
171
|
+
q_leaf, s_leaf, m_leaf = STORAGE_LEAVES
|
|
172
|
+
bases = [k[: -len("." + q_leaf)] for k in weights
|
|
173
|
+
if k.endswith("." + q_leaf)]
|
|
174
|
+
for base in bases:
|
|
175
|
+
qw = weights.pop(f"{base}.{q_leaf}")
|
|
176
|
+
try:
|
|
177
|
+
sc = weights.pop(f"{base}.{s_leaf}")
|
|
178
|
+
mn = weights.pop(f"{base}.{m_leaf}")
|
|
179
|
+
except KeyError as e:
|
|
180
|
+
raise RuntimeError(
|
|
181
|
+
f"ZERO FALLBACK: incomplete quantized triplet for "
|
|
182
|
+
f"'{base}' — missing {e}. The build gate should have "
|
|
183
|
+
f"refused this artifact; re-build the variant.") from e
|
|
184
|
+
kp, n = qw.shape
|
|
185
|
+
weights[base + ".weight"] = QuantizedTensor(
|
|
186
|
+
qw, sc, mn, logical_shape=(n, kp * PACK))
|
|
187
|
+
return len(bases)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Random: seeded streams (the vendor CPU generator's MT19937) and torch-free draws."""
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""ONE draw for both engines: the vendor's CPU draw, torch-free.
|
|
2
|
+
|
|
3
|
+
The owner's rule (2026-10-13 02:05): a model's initial noise is one draw, data-driven (the request's
|
|
4
|
+
seed), identical between the compiled and the Triton branch and to the vendor at equal seed. Diffusers
|
|
5
|
+
draws its initial latents on torch's CPU generator — `torch.Generator("cpu").manual_seed(seed)` then
|
|
6
|
+
`randn` — which is MT19937 seeded by `init_genrand(seed)` (the low 32 bits), uniforms made of the 24
|
|
7
|
+
high mantissa bits of a 32-bit word, and ATen's `normal_fill`: Box-Muller over blocks of 16 (the first
|
|
8
|
+
eight uniforms of a block are the radii, the last eight the angles), the tail of `n % 16` values
|
|
9
|
+
recomputed from 16 FRESH uniforms. This module reproduces that stream without torch.
|
|
10
|
+
|
|
11
|
+
What is bit-exact: the uniform stream (every word of MT19937, every 24-bit mantissa) and the pairing.
|
|
12
|
+
What is within 1 ulp: the transcendentals. ATen computes `sqrt(-2 log u1)`, `cos`, `sin` through its
|
|
13
|
+
vectorised Sleef kernels (`logf_u10`, `sinf/cosf_u35`, AVX512 here, NEON on Apple), which are not
|
|
14
|
+
correctly rounded and differ per architecture; this module computes them in float64 and rounds once,
|
|
15
|
+
which is correctly rounded everywhere. Measured against torch 2.14 on an AVX512 Xeon over 100 000 values
|
|
16
|
+
per seed (seeds 7, 42, 2^31-1; 2026-10-13): the uniform stream bit-equal, the normals at most 3 ulp
|
|
17
|
+
apart (Sleef's u35 bound is 3.5), 98.8 % within 1 ulp, 61.5 % bit-equal, max |diff| 4.8e-7. The draw is
|
|
18
|
+
therefore identical between our two branches and on every platform, and within 4 ulp of the vendor's.
|
|
19
|
+
|
|
20
|
+
No torch, no device: numpy only (R33 holds; the compiled branch wraps the array in a tensor).
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import math
|
|
25
|
+
from typing import Iterable, Tuple
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
_N, _M = 624, 397
|
|
30
|
+
_MATRIX_A = np.uint32(0x9908B0DF)
|
|
31
|
+
_UPPER, _LOWER = np.uint32(0x80000000), np.uint32(0x7FFFFFFF)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MT19937:
|
|
35
|
+
"""The generator as torch's CPU `Generator` runs it: `manual_seed(s)` = `init_genrand(s & 0xffffffff)`."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, seed: int) -> None:
|
|
38
|
+
mt = np.zeros(_N, dtype=np.uint32)
|
|
39
|
+
mt[0] = np.uint32(int(seed) & 0xFFFFFFFF)
|
|
40
|
+
for i in range(1, _N): # the recurrence is sequential by definition; 624 steps once per run
|
|
41
|
+
prev = int(mt[i - 1])
|
|
42
|
+
mt[i] = np.uint32((1812433253 * (prev ^ (prev >> 30)) + i) & 0xFFFFFFFF)
|
|
43
|
+
self._mt = mt
|
|
44
|
+
self._i = _N
|
|
45
|
+
self._cached = None # the second Box-Muller sample of the scalar path, as torch caches it
|
|
46
|
+
|
|
47
|
+
def _twist(self) -> None:
|
|
48
|
+
"""The reference twist, vectorised in the order it reads: `mt[k] = mt[(k + M) % N] ^ f(y_k)` with
|
|
49
|
+
`y_k` over the ORIGINAL `mt[k], mt[k+1]` for every k but the last. The second segment reads entries
|
|
50
|
+
rewritten earlier in the same pass, so it runs in chunks of N-M, each reading only chunks already
|
|
51
|
+
produced (a single vector expression read uninitialised values — the stream departed at word 454,
|
|
52
|
+
2026-10-13)."""
|
|
53
|
+
mt = self._mt
|
|
54
|
+
y = (mt & _UPPER) | (np.roll(mt, -1) & _LOWER)
|
|
55
|
+
shifted = (y >> np.uint32(1)) ^ np.where(y & np.uint32(1), _MATRIX_A, np.uint32(0))
|
|
56
|
+
new = np.empty_like(mt)
|
|
57
|
+
s = _N - _M # 227: the stride a segment reads behind itself
|
|
58
|
+
new[:s] = mt[_M:] ^ shifted[:s]
|
|
59
|
+
k = s
|
|
60
|
+
while k < _N - 1:
|
|
61
|
+
e = min(k + s, _N - 1)
|
|
62
|
+
new[k:e] = new[k - s : e - s] ^ shifted[k:e]
|
|
63
|
+
k = e
|
|
64
|
+
y_last = (mt[_N - 1] & _UPPER) | (new[0] & _LOWER)
|
|
65
|
+
new[_N - 1] = new[_M - 1] ^ ((y_last >> np.uint32(1)) ^ (_MATRIX_A if int(y_last) & 1 else np.uint32(0)))
|
|
66
|
+
self._mt = new
|
|
67
|
+
self._i = 0
|
|
68
|
+
|
|
69
|
+
def words(self, n: int) -> np.ndarray:
|
|
70
|
+
"""`n` tempered 32-bit words, in stream order."""
|
|
71
|
+
out = np.empty(n, dtype=np.uint32)
|
|
72
|
+
k = 0
|
|
73
|
+
while k < n:
|
|
74
|
+
if self._i >= _N:
|
|
75
|
+
self._twist()
|
|
76
|
+
take = min(n - k, _N - self._i)
|
|
77
|
+
y = self._mt[self._i : self._i + take].copy()
|
|
78
|
+
y ^= y >> np.uint32(11)
|
|
79
|
+
y ^= (y << np.uint32(7)) & np.uint32(0x9D2C5680)
|
|
80
|
+
y ^= (y << np.uint32(15)) & np.uint32(0xEFC60000)
|
|
81
|
+
y ^= y >> np.uint32(18)
|
|
82
|
+
out[k : k + take] = y
|
|
83
|
+
self._i += take
|
|
84
|
+
k += take
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
def uniform_f32(self, n: int) -> np.ndarray:
|
|
88
|
+
"""ATen `uniform_real_distribution<float>(0, 1)`: the 24 low bits of each word over 2^24."""
|
|
89
|
+
return ((self.words(n) & np.uint32((1 << 24) - 1)).astype(np.float64) * (1.0 / (1 << 24))).astype(np.float32)
|
|
90
|
+
|
|
91
|
+
def randn(self, n: int) -> np.ndarray:
|
|
92
|
+
"""`n` standard normals as torch's CPU `randn` draws them (ATen `normal_fill`, blocks of 16; a tail of
|
|
93
|
+
`n % 16` recomputed from 16 fresh uniforms), the transcendentals in float64 rounded once."""
|
|
94
|
+
if n < 16:
|
|
95
|
+
return self._randn_scalar(n)
|
|
96
|
+
u = self.uniform_f32(n)
|
|
97
|
+
out = np.empty(n, dtype=np.float32)
|
|
98
|
+
m = n - n % 16
|
|
99
|
+
if m:
|
|
100
|
+
_fill16(out, 0, u[:m])
|
|
101
|
+
if n % 16:
|
|
102
|
+
_fill16(out, n - 16, self.uniform_f32(16))
|
|
103
|
+
return out
|
|
104
|
+
|
|
105
|
+
def _randn_scalar(self, n: int) -> np.ndarray:
|
|
106
|
+
"""ATen's path for fewer than 16 values (`normal_kernel`'s serial branch): `normal_distribution<double>`
|
|
107
|
+
over 53-bit uniforms (two words per uniform, `random64`), Box-Muller in double with the second sample
|
|
108
|
+
of each pair cached in the generator and served next — exactly the vendor's small draws."""
|
|
109
|
+
out = np.empty(n, dtype=np.float32)
|
|
110
|
+
i = 0
|
|
111
|
+
while i < n:
|
|
112
|
+
if self._cached is not None:
|
|
113
|
+
out[i] = np.float32(self._cached)
|
|
114
|
+
self._cached = None
|
|
115
|
+
i += 1
|
|
116
|
+
continue
|
|
117
|
+
# ATen normal_distribution<double>: u1 then u2; r = sqrt(-2 log1p(-u2)); theta = 2 pi u1;
|
|
118
|
+
# the sine sample cached for the next call, the cosine returned.
|
|
119
|
+
u1 = self._uniform_f64()
|
|
120
|
+
u2 = self._uniform_f64()
|
|
121
|
+
radius = math.sqrt(-2.0 * math.log1p(-u2))
|
|
122
|
+
theta = 2.0 * math.pi * u1
|
|
123
|
+
self._cached = radius * math.sin(theta)
|
|
124
|
+
out[i] = np.float32(radius * math.cos(theta))
|
|
125
|
+
i += 1
|
|
126
|
+
return out
|
|
127
|
+
|
|
128
|
+
def _uniform_f64(self) -> float:
|
|
129
|
+
"""ATen `uniform_real_distribution<double>(0, 1)`: the low 53 bits of a 64-bit word made of two 32-bit
|
|
130
|
+
words (`random64()` = `make64BitsFrom32Bits(random(), random())`: the first word high, the second low)."""
|
|
131
|
+
hi, lo = (int(x) for x in self.words(2))
|
|
132
|
+
v = ((hi << 32) | lo) & ((1 << 53) - 1)
|
|
133
|
+
return v * (1.0 / (1 << 53))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _fill16(dst: np.ndarray, base: int, u: np.ndarray) -> None:
|
|
137
|
+
b = u.reshape(-1, 16)
|
|
138
|
+
u1 = (np.float32(1.0) - b[:, :8]).astype(np.float64)
|
|
139
|
+
u2 = b[:, 8:].astype(np.float32)
|
|
140
|
+
radius = np.sqrt(-2.0 * np.log(u1))
|
|
141
|
+
theta = (np.float32(2.0) * np.float32(math.pi) * u2).astype(np.float64) # ATen: 2.0f * pi<double> * u2 → float
|
|
142
|
+
view = dst[base : base + b.size].reshape(-1, 16)
|
|
143
|
+
view[:, :8] = (radius * np.cos(theta)).astype(np.float32)
|
|
144
|
+
view[:, 8:] = (radius * np.sin(theta)).astype(np.float32)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def mt_randn(seed: int, shape: Iterable[int]) -> np.ndarray:
|
|
148
|
+
"""A fresh generator seeded with `seed`, its first `prod(shape)` normals reshaped — the vendor's
|
|
149
|
+
`randn_tensor(shape, generator=torch.Generator("cpu").manual_seed(seed))`, torch-free."""
|
|
150
|
+
shape: Tuple[int, ...] = tuple(int(s) for s in shape)
|
|
151
|
+
n = 1
|
|
152
|
+
for s in shape:
|
|
153
|
+
n *= s
|
|
154
|
+
return MT19937(seed).randn(n).reshape(shape)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Gated cross-engine deterministic RNG for noise pinning.
|
|
2
|
+
|
|
3
|
+
`NBX_FORCE_RAND_SEED=<int>` makes every `rand`/`randn`/`*_like` op draw from a
|
|
4
|
+
single `numpy.RandomState`, so the PyTorch and Triton engines produce IDENTICAL
|
|
5
|
+
noise when they execute the same DAG (same op order + shapes). Two uses:
|
|
6
|
+
|
|
7
|
+
1. Diagnostic — pin the noise so an op-by-op cross-engine diff is meaningful
|
|
8
|
+
past the first random op (otherwise every downstream op "diverges" purely
|
|
9
|
+
because each engine drew different noise, masking real kernel bugs).
|
|
10
|
+
2. Shared-seeded determinism — the production way to make a stochastic vocoder
|
|
11
|
+
(e.g. chatterbox s3gen CFM + NSF/SineGen source) reproducible and equal
|
|
12
|
+
across all four execution modes, same discipline as the openaudio dual_ar /
|
|
13
|
+
VibeVoice diffusion seeded noise.
|
|
14
|
+
|
|
15
|
+
Zero torch: this module returns numpy arrays. The Triton side wraps them as
|
|
16
|
+
`NBXTensor.from_numpy`; the PyTorch side wraps them as `torch.from_numpy`. The
|
|
17
|
+
draw order/shape must match across engines for the arrays to be bit-identical;
|
|
18
|
+
both run the same component DAG, so they do.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import numpy as np
|
|
23
|
+
|
|
24
|
+
_STATE = None
|
|
25
|
+
_SEED_USED = "<unset>"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def pinned_seed():
|
|
29
|
+
"""Return the configured seed (int) or None when pinning is disabled."""
|
|
30
|
+
v = os.environ.get("NBX_FORCE_RAND_SEED")
|
|
31
|
+
if v is None or v == "":
|
|
32
|
+
return None
|
|
33
|
+
try:
|
|
34
|
+
return int(v)
|
|
35
|
+
except ValueError:
|
|
36
|
+
return 1234
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _state():
|
|
40
|
+
global _STATE, _SEED_USED
|
|
41
|
+
s = pinned_seed()
|
|
42
|
+
if _STATE is None or _SEED_USED != s:
|
|
43
|
+
_STATE = np.random.RandomState(s if s is not None else 1234)
|
|
44
|
+
_SEED_USED = s
|
|
45
|
+
return _STATE
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _shape(shape):
|
|
49
|
+
return tuple(int(d) for d in shape)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def pinned_normal(shape):
|
|
53
|
+
"""Standard-normal N(0,1) float32 array of the given shape."""
|
|
54
|
+
return _state().standard_normal(size=_shape(shape)).astype(np.float32)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pinned_uniform(shape):
|
|
58
|
+
"""Uniform [0,1) float32 array of the given shape."""
|
|
59
|
+
return _state().random_sample(size=_shape(shape)).astype(np.float32)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Run-scoped seeded RNG stream for the Triton random wrappers.
|
|
2
|
+
|
|
3
|
+
The random wrappers (`rand/randn/normal/*_like` in `kernels/wrappers.py`)
|
|
4
|
+
historically drew each call's Philox seed from Python's `random.randint`,
|
|
5
|
+
which `--seed` never seeds: Triton-mode stochastic sampling (EulerAncestral
|
|
6
|
+
noise, DDIM eta>0 variance) was NON-reproducible by construction, and no
|
|
7
|
+
run-scoped stream contract existed at all.
|
|
8
|
+
|
|
9
|
+
This module is the Triton-engine mirror of the compiled engine's run-scoped
|
|
10
|
+
`torch.Generator` (see `VariableResolver.sampling_generator`): ONE seeded
|
|
11
|
+
stream drives every stochastic draw of a run, in consumption order. The flow
|
|
12
|
+
handler arms it at execution start from `defaults.seed` (the same data-driven
|
|
13
|
+
source both engines read); each wrapper call then derives an independent
|
|
14
|
+
per-draw kernel seed as splitmix64(run_seed, draw_counter). Draws are
|
|
15
|
+
reproducible per (seed, draw order) and statistically independent across the
|
|
16
|
+
counter. Cross-ENGINE bit-equality is NOT the contract here (the two engines
|
|
17
|
+
use different RNG algorithms); when a bit-identical cross-engine noise diff
|
|
18
|
+
is needed, the `rng_pin` module (NBX_FORCE_RAND_SEED) remains the tool and
|
|
19
|
+
takes precedence in the wrappers.
|
|
20
|
+
|
|
21
|
+
Zero torch, zero device state: pure Python integers.
|
|
22
|
+
|
|
23
|
+
THE ONE DRAW (owner, 2026-10-13 02:05): a model's initial noise is ONE draw, data-driven (the run's
|
|
24
|
+
seed), identical between the compiled and the Triton branch and to the vendor at equal seed. Both
|
|
25
|
+
engines take it from `init_draw(shape)` below — the vendor's CPU draw reproduced torch-free
|
|
26
|
+
(`nbxtensor/random/mt_randn.py`: MT19937 + ATen's normal_fill, within 4 ulp of `torch.randn(generator=cpu)`)
|
|
27
|
+
— as the first draw of the run's seeded stream, then the next for every further `init: randn`
|
|
28
|
+
variable, in consumption order. The per-draw Philox seeds above still serve the Triton random
|
|
29
|
+
kernels of the hot loop (scheduler noise); the compiled branch's scheduler draws keep its generator.
|
|
30
|
+
Cross-engine bit-equality of the INITIAL draw is the contract from here on.
|
|
31
|
+
|
|
32
|
+
EVERY DRAW OF A SEEDED RUN (2026-10-14): the vendor threads ONE generator through every draw of a run — an
|
|
33
|
+
image encoder's posterior sample (`retrieve_latents(vae.encode(x), generator)`), the initial noise, each
|
|
34
|
+
ancestral step's noise (`EulerAncestralDiscreteScheduler.step(..., generator)`). Measured on Allegro-TI2V at
|
|
35
|
+
its gate request: the two engines rendered the same clip at one step (mean |a - b| under 1 of 255 per frame)
|
|
36
|
+
and different content at ten, the ancestral noise being the only difference (torch's device generator in the
|
|
37
|
+
compiled branch, Philox in the Triton branch); on CogVideoX-5b-I2V the first op that departs between the
|
|
38
|
+
kernel oracle and the Triton branch is the posterior sample `vae_encoder/aten.randn_like::0`. So every draw of a
|
|
39
|
+
seeded run — graph random ops and scheduler noise, in both engines — is the run stream's next normal
|
|
40
|
+
(`next_normal`) or uniform (`next_uniform`) values, in consumption order. The per-draw Philox seeds
|
|
41
|
+
(`next_seed`) serve only what is not a seeded run's draw.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
_RUN_SEED = None
|
|
45
|
+
_COUNTER = 0
|
|
46
|
+
_MT = None # the run's MT19937, armed with the seed; the init draws come from it in order
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def set_run_seed(seed):
|
|
50
|
+
"""Arm (or disarm with None) the run stream; resets the draw counter and the init-draw generator."""
|
|
51
|
+
global _RUN_SEED, _COUNTER, _MT
|
|
52
|
+
_RUN_SEED = int(seed) if seed is not None else None
|
|
53
|
+
_COUNTER = 0
|
|
54
|
+
_MT = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _mt():
|
|
58
|
+
global _MT
|
|
59
|
+
if _RUN_SEED is None:
|
|
60
|
+
raise RuntimeError("rng_stream: a draw while inactive: the run has no seed")
|
|
61
|
+
if _MT is None:
|
|
62
|
+
from nbxtensor.random.mt_randn import MT19937
|
|
63
|
+
_MT = MT19937(_RUN_SEED)
|
|
64
|
+
return _MT
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _count(shape):
|
|
68
|
+
n = 1
|
|
69
|
+
for x in shape:
|
|
70
|
+
n *= int(x)
|
|
71
|
+
return n
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def next_normal(shape):
|
|
75
|
+
"""The run stream's next standard normals as a float32 numpy array of `shape` (torch's CPU `randn` for the
|
|
76
|
+
run's seed, continued): the draw of every seeded random op of either engine."""
|
|
77
|
+
shape = tuple(int(x) for x in shape)
|
|
78
|
+
return _mt().randn(_count(shape)).reshape(shape)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def next_uniform(shape):
|
|
82
|
+
"""The run stream's next [0, 1) uniforms as a float32 numpy array of `shape` (torch's CPU `rand`, continued)."""
|
|
83
|
+
shape = tuple(int(x) for x in shape)
|
|
84
|
+
return _mt().uniform_f32(_count(shape)).reshape(shape)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def init_draw(shape):
|
|
88
|
+
"""The run's next initial-noise draw as a float32 numpy array of `shape` — the vendor's CPU draw for
|
|
89
|
+
the run's seed (`nbxtensor/random/mt_randn.MT19937`), the same bytes in both engines. Refused when no seed is
|
|
90
|
+
armed: a seedless run draws unseeded in each engine (the historical behaviour), and that is said."""
|
|
91
|
+
global _MT
|
|
92
|
+
if _RUN_SEED is None:
|
|
93
|
+
raise RuntimeError("rng_stream.init_draw() called while inactive: the run has no seed")
|
|
94
|
+
if _MT is None:
|
|
95
|
+
from nbxtensor.random.mt_randn import MT19937
|
|
96
|
+
_MT = MT19937(_RUN_SEED)
|
|
97
|
+
shape = tuple(int(x) for x in shape)
|
|
98
|
+
n = 1
|
|
99
|
+
for x in shape:
|
|
100
|
+
n *= x
|
|
101
|
+
return _MT.randn(n).reshape(shape)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def active() -> bool:
|
|
105
|
+
return _RUN_SEED is not None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def next_seed() -> int:
|
|
109
|
+
"""Next per-draw kernel seed: splitmix64 of (run_seed, counter), 31-bit."""
|
|
110
|
+
global _COUNTER
|
|
111
|
+
if _RUN_SEED is None:
|
|
112
|
+
raise RuntimeError("rng_stream.next_seed() called while inactive")
|
|
113
|
+
x = (_RUN_SEED * 0x9E3779B97F4A7C15 + _COUNTER * 0xBF58476D1CE4E5B9) & 0xFFFFFFFFFFFFFFFF
|
|
114
|
+
_COUNTER += 1
|
|
115
|
+
x = ((x ^ (x >> 30)) * 0xBF58476D1CE4E5B9) & 0xFFFFFFFFFFFFFFFF
|
|
116
|
+
x = ((x ^ (x >> 27)) * 0x94D049BB133111EB) & 0xFFFFFFFFFFFFFFFF
|
|
117
|
+
x = x ^ (x >> 31)
|
|
118
|
+
return int(x & 0x7FFFFFFF)
|