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,1370 @@
|
|
|
1
|
+
"""Linear-algebra wrappers: matmul, bmm, addmm, baddbmm, addmv, mv, dot, gemv. Cut out of wrappers.py by family (M2, 2026-10-29), unchanged; shared launch layer and run context stay in wrappers.py."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import triton
|
|
7
|
+
from nbxtensor.ops.autotune_bucket import bucket_of as _bucket_of
|
|
8
|
+
from nbxtensor.ops import launch_keys as _lk
|
|
9
|
+
from nbxtensor.tensor import NBXTensor
|
|
10
|
+
from nbxtensor.dtype.nbx_dtype import NBXDtype, dtype_size
|
|
11
|
+
from nbxtensor.memory.allocator import DeviceAllocator
|
|
12
|
+
from nbxtensor.backend.runtime import set_device
|
|
13
|
+
from nbxtensor.shape import MAX_NDIM as _NBX_MAX_NDIM, broadcast_shapes, max_linear_offset
|
|
14
|
+
from nbxtensor.dtype.itemsize import canonical_name
|
|
15
|
+
from nbxtensor.dtype.promotion import binary_result, float_math_input
|
|
16
|
+
from nbxtensor.dtype.nbx_dtype import parse_dtype
|
|
17
|
+
from nbxtensor.memory.allocator import DeviceOOMError
|
|
18
|
+
from nbxtensor.backend.capabilities import fa_min_tile as _fa_min_tile
|
|
19
|
+
from nbxtensor.backend.capabilities import reduce_tile_max as _reduce_tile_max
|
|
20
|
+
from nbxtensor.backend.faults import device_fault_buffer, device_fault_code_cached, fault_channel
|
|
21
|
+
from nbxtensor.tensor import _upload_int64_array as _nbx_upload_int64, _saturating_cast as _nbx_saturating_cast
|
|
22
|
+
from nbxtensor.ops._configs import sdpa_block_ceiling as _sdpa_block_ceiling
|
|
23
|
+
from nbxtensor.ops._configs import sdpa_launch_meta as _sdpa_launch_meta
|
|
24
|
+
from nbxtensor.ops._configs import matrix_unit as _matrix_unit, matrix_unit_mm_tile as _matrix_unit_mm_tile
|
|
25
|
+
from nbxtensor.ops._configs import largest_tile_within_smem as _largest_tile_within_smem
|
|
26
|
+
from nbxtensor.backend import launcher as _nbx_launcher
|
|
27
|
+
from nbxtensor.backend.launcher import install as _install_launcher
|
|
28
|
+
from nbxtensor.ops.relu import relu_forward_kernel
|
|
29
|
+
from nbxtensor.ops.silu import silu_forward_kernel
|
|
30
|
+
from nbxtensor.ops.gelu import gelu_forward_kernel
|
|
31
|
+
from nbxtensor.ops.sigmoid import sigmoid_forward_kernel
|
|
32
|
+
from nbxtensor.ops.tanh import tanh_forward_kernel
|
|
33
|
+
from nbxtensor.ops.hardsigmoid import hardsigmoid_forward_kernel
|
|
34
|
+
from nbxtensor.ops.hardswish import hardswish_forward_kernel
|
|
35
|
+
from nbxtensor.ops.leaky_relu import leaky_relu_forward_kernel
|
|
36
|
+
from nbxtensor.ops.elu import elu_forward_kernel
|
|
37
|
+
from nbxtensor.ops.mish import mish_forward_kernel
|
|
38
|
+
from nbxtensor.ops.selu import selu_forward_kernel
|
|
39
|
+
from nbxtensor.ops.neg import neg_forward_kernel
|
|
40
|
+
from nbxtensor.ops.exp import exp_forward_kernel
|
|
41
|
+
from nbxtensor.ops.sin import sin_forward_kernel
|
|
42
|
+
from nbxtensor.ops.cos import cos_forward_kernel
|
|
43
|
+
from nbxtensor.ops.rsqrt import rsqrt_forward_kernel
|
|
44
|
+
from nbxtensor.ops.sqrt import sqrt_forward_kernel
|
|
45
|
+
from nbxtensor.ops.abs import abs_forward_kernel
|
|
46
|
+
from nbxtensor.ops.log import log_forward_kernel
|
|
47
|
+
from nbxtensor.ops.reciprocal import reciprocal_forward_kernel
|
|
48
|
+
from nbxtensor.ops.pow import pow_forward_kernel
|
|
49
|
+
from nbxtensor.ops.clamp import clamp_forward_kernel
|
|
50
|
+
from nbxtensor.ops.erf import erf_forward_kernel
|
|
51
|
+
from nbxtensor.ops.copy import copy_forward_kernel
|
|
52
|
+
from nbxtensor.ops.add import add_forward_kernel, add_scalar_kernel, add_scalar_dev_kernel, add_bias_broadcast_kernel
|
|
53
|
+
from nbxtensor.ops.binary_strided import add_strided_nd_kernel, mul_strided_nd_kernel, sub_strided_nd_kernel, div_strided_nd_kernel, add_scalar_strided_nd_kernel, mul_scalar_strided_nd_kernel
|
|
54
|
+
from nbxtensor.ops.mul import mul_forward_kernel, mul_scalar_kernel, mul_scalar_dev_kernel
|
|
55
|
+
from nbxtensor.ops.div import div_forward_kernel, div_scalar_kernel, div_scalar_dev_kernel, int_div_kernel
|
|
56
|
+
from nbxtensor.ops.sub import sub_forward_kernel, rsub_forward_kernel
|
|
57
|
+
from nbxtensor.ops.where import where_forward_kernel
|
|
58
|
+
from nbxtensor.ops.maximum import maximum_forward_kernel
|
|
59
|
+
from nbxtensor.ops.minimum import minimum_forward_kernel
|
|
60
|
+
from nbxtensor.ops.masked_fill import masked_fill_forward_kernel
|
|
61
|
+
from nbxtensor.ops.gt import gt_forward_kernel, gt_scalar_kernel
|
|
62
|
+
from nbxtensor.ops.ge import ge_forward_kernel, ge_scalar_kernel
|
|
63
|
+
from nbxtensor.ops.lt import lt_forward_kernel, lt_scalar_kernel
|
|
64
|
+
from nbxtensor.ops.eq import eq_forward_kernel, eq_scalar_kernel
|
|
65
|
+
from nbxtensor.ops.ne import ne_forward_kernel, ne_scalar_kernel
|
|
66
|
+
from nbxtensor.ops.layernorm import layer_norm_forward_kernel
|
|
67
|
+
from nbxtensor.ops.rmsnorm import rms_norm_forward_kernel
|
|
68
|
+
from nbxtensor.ops.softmax import softmax_forward_kernel, softmax_forward_looped_kernel
|
|
69
|
+
from nbxtensor.ops.matmul import matmul_kernel, addmm_kernel
|
|
70
|
+
from nbxtensor.ops._autotune_policy import NBX_FORCE_FP32_ACCUM
|
|
71
|
+
from nbxtensor.ops.fused_moe import fused_moe_kernel, silu_and_mul_kernel, silu_mul_split_kernel
|
|
72
|
+
from nbxtensor.ops.dtype_convert import bf16_to_fp16_kernel
|
|
73
|
+
from nbxtensor.ops.embedding import embedding_kernel, EMBEDDING_OOB
|
|
74
|
+
from nbxtensor.ops.sum import sum_forward_kernel
|
|
75
|
+
from nbxtensor.ops.mean import mean_forward_kernel
|
|
76
|
+
from nbxtensor.ops.amax import amax_forward_kernel
|
|
77
|
+
from nbxtensor.ops.glu import glu_forward_kernel
|
|
78
|
+
from nbxtensor.ops.le import le_forward_kernel, le_scalar_kernel
|
|
79
|
+
from nbxtensor.ops.softplus import softplus_forward_kernel
|
|
80
|
+
from nbxtensor.ops.dropout import dropout_inference_kernel
|
|
81
|
+
from nbxtensor.ops.upsample_nearest2d import upsample_nearest2d_kernel
|
|
82
|
+
from nbxtensor.ops.groupnorm import group_norm_forward_kernel
|
|
83
|
+
from nbxtensor.ops.index_select import index_select_kernel, INDEX_SELECT_OOB
|
|
84
|
+
from nbxtensor.ops.triu import triu_kernel, triu_batch_kernel
|
|
85
|
+
from nbxtensor.ops.tril import tril_kernel, tril_batch_kernel
|
|
86
|
+
from nbxtensor.ops.argmax import argmax_kernel_1, argmax_kernel_2, argmax_kernel_inner
|
|
87
|
+
from nbxtensor.ops.argmin import argmin_kernel_1, argmin_kernel_2, argmin_kernel
|
|
88
|
+
from nbxtensor.ops.conv2d import conv2d_forward_kernel
|
|
89
|
+
from nbxtensor.ops.depthwise_conv2d import depthwise_conv2d_kernel
|
|
90
|
+
from nbxtensor.ops.batch_norm import batch_norm_forward_kernel
|
|
91
|
+
from nbxtensor.ops.cumsum import scan_part_sum_kernel, add_base_sum_kernel
|
|
92
|
+
from nbxtensor.ops.scatter_op import scatter_kernel, scatter_add_kernel, scatter_reduce_amax_kernel, scatter_reduce_amin_kernel
|
|
93
|
+
from nbxtensor.ops.gather_op import gather_kernel
|
|
94
|
+
from nbxtensor.ops.avg_pool2d import avg_pool2d_forward_kernel
|
|
95
|
+
from nbxtensor.ops.max_pool2d import max_pool2d_forward_kernel
|
|
96
|
+
from nbxtensor.ops.max_pool3d import max_pool3d_forward_kernel
|
|
97
|
+
from nbxtensor.ops.cross_entropy import cross_entropy_loss_forward_kernel
|
|
98
|
+
from nbxtensor.ops.exp2 import exp2_forward_kernel
|
|
99
|
+
from nbxtensor.ops.tan import tan_forward_kernel
|
|
100
|
+
from nbxtensor.ops.celu import celu_forward_kernel
|
|
101
|
+
from nbxtensor.ops.log_sigmoid import log_sigmoid_forward_kernel
|
|
102
|
+
from nbxtensor.ops.isfinite import isfinite_forward_kernel
|
|
103
|
+
from nbxtensor.ops.isinf import isinf_forward_kernel
|
|
104
|
+
from nbxtensor.ops.isnan import isnan_forward_kernel
|
|
105
|
+
from nbxtensor.ops.nan_to_num import nan_to_num_forward_kernel
|
|
106
|
+
from nbxtensor.ops.threshold import threshold_forward_kernel
|
|
107
|
+
from nbxtensor.ops.floor import floor_forward_kernel, ceil_forward_kernel, round_forward_kernel, trunc_forward_kernel
|
|
108
|
+
from nbxtensor.ops.addcdiv import addcdiv_forward_kernel
|
|
109
|
+
from nbxtensor.ops.addcmul import addcmul_forward_kernel
|
|
110
|
+
from nbxtensor.ops.all_reduce import all_kernel_mid, all_kernel_result, all_kernel_dim
|
|
111
|
+
from nbxtensor.ops.any_reduce import any_kernel_mid, any_kernel_result, any_kernel_dim
|
|
112
|
+
from nbxtensor.ops.bitwise_and import bitwise_and_forward_kernel
|
|
113
|
+
from nbxtensor.ops.bitwise_or import bitwise_or_forward_kernel
|
|
114
|
+
from nbxtensor.ops.bitwise_not import bitwise_not_forward_kernel
|
|
115
|
+
from nbxtensor.ops.logical_and import logical_and_forward_kernel
|
|
116
|
+
from nbxtensor.ops.logical_or import logical_or_forward_kernel
|
|
117
|
+
from nbxtensor.ops.logical_not import logical_not_forward_kernel
|
|
118
|
+
from nbxtensor.ops.lerp import lerp_tensor_forward_kernel, lerp_scalar_head_kernel, lerp_scalar_tail_kernel
|
|
119
|
+
from nbxtensor.ops.dot_op import dot_kernel_small, dot_kernel_partial, dot_kernel_reduce
|
|
120
|
+
from nbxtensor.ops.flip_op import flip_1d_kernel, flip_strided_kernel
|
|
121
|
+
from nbxtensor.ops.cat_op import cat_copy_kernel_4
|
|
122
|
+
from nbxtensor.ops.fill_op import fill_kernel
|
|
123
|
+
from nbxtensor.ops.arange_op import arange_kernel
|
|
124
|
+
from nbxtensor.ops.prod import prod_kernel_mid, prod_kernel_result, prod_kernel
|
|
125
|
+
from nbxtensor.ops.min_reduce import min_kernel_mid, min_kernel_result, min_kernel
|
|
126
|
+
from nbxtensor.ops.max_reduce import max_kernel_mid, max_kernel_result, max_kernel
|
|
127
|
+
from nbxtensor.ops.baddbmm_op import baddbmm_kernel
|
|
128
|
+
from nbxtensor.ops.addmv_op import addmv_kernel
|
|
129
|
+
from nbxtensor.ops.mse_loss import mse_loss_partial_kernel, mse_loss_reduce_kernel, mse_loss_none_kernel
|
|
130
|
+
from nbxtensor.ops.nllloss import nll_loss_forward_kernel
|
|
131
|
+
from nbxtensor.ops.std import std_map_kernel, std_reduce_kernel, std_dim_kernel
|
|
132
|
+
from nbxtensor.ops.var import var_kernel_1, var_kernel_2, var_welford_kernel
|
|
133
|
+
from nbxtensor.ops.index_add import index_add_gather_kernel
|
|
134
|
+
from nbxtensor.ops.index_put_op import index_put_kernel, INDEX_PUT_OOB
|
|
135
|
+
from nbxtensor.ops.sort_op import (radix_sort_histogram_kernel,
|
|
136
|
+
radix_sort_scatter_kernel,
|
|
137
|
+
radix_sort_tile_counts_kernel,
|
|
138
|
+
radix_sort_tile_prefix_kernel)
|
|
139
|
+
from nbxtensor.ops.rope import rope_forward_kernel
|
|
140
|
+
from nbxtensor.ops.pixel_shuffle import (
|
|
141
|
+
pixel_shuffle_kernel,
|
|
142
|
+
pixel_shuffle_broadcast_aware_kernel,
|
|
143
|
+
)
|
|
144
|
+
from nbxtensor.ops.pixel_unshuffle import pixel_unshuffle_kernel
|
|
145
|
+
from nbxtensor.ops.upsample_bilinear2d import upsample_bilinear2d_kernel
|
|
146
|
+
from nbxtensor.ops.adaptive_avg_pool2d import adaptive_avg_pool2d_kernel
|
|
147
|
+
from nbxtensor.ops.conv_depthwise2d import conv_depthwise2d_kernel
|
|
148
|
+
from nbxtensor.ops.scaled_softmax import scaled_softmax_kernel
|
|
149
|
+
from nbxtensor.ops.vector_norm import (
|
|
150
|
+
l2_norm_kernel, l1_norm_kernel, linf_norm_kernel, l0_norm_kernel, lp_norm_kernel,
|
|
151
|
+
l2_norm_pass1_kernel, l2_norm_pass2_kernel,
|
|
152
|
+
linf_norm_pass1_kernel, linf_norm_pass2_kernel,
|
|
153
|
+
)
|
|
154
|
+
from nbxtensor.ops.addr import addr_kernel
|
|
155
|
+
from nbxtensor.ops.logical_xor import logical_xor_kernel
|
|
156
|
+
from nbxtensor.ops.bitwise_left_shift import bitwise_left_shift_kernel
|
|
157
|
+
from nbxtensor.ops.bitwise_right_shift import bitwise_right_shift_kernel
|
|
158
|
+
from nbxtensor.ops.rand_op import rand_kernel, randn_kernel
|
|
159
|
+
from nbxtensor.ops.clamp_min import clamp_min_forward_kernel
|
|
160
|
+
from nbxtensor.ops.conv_transpose2d import conv_transpose2d_kernel
|
|
161
|
+
from nbxtensor.ops.cummax import scan_part_max_kernel
|
|
162
|
+
from nbxtensor.ops.cummin import scan_part_min_kernel
|
|
163
|
+
from nbxtensor.ops.count_nonzero import count_nonzero_kernel
|
|
164
|
+
from nbxtensor.ops.trace_op import trace_kernel
|
|
165
|
+
from nbxtensor.ops.linspace_op import linspace_kernel
|
|
166
|
+
from nbxtensor.ops.full_op import full_kernel
|
|
167
|
+
from nbxtensor.ops.stack_op import stack_copy_kernel
|
|
168
|
+
from nbxtensor.ops.repeat_op import repeat_1d_kernel
|
|
169
|
+
from nbxtensor.ops.upsample_nearest3d import upsample_nearest3d_kernel
|
|
170
|
+
from nbxtensor.ops.weight_norm import weight_norm_kernel_first, weight_norm_kernel_last
|
|
171
|
+
from nbxtensor.ops.repeat_interleave import repeat_interleave_tensor_kernel
|
|
172
|
+
from nbxtensor.ops.elementwise_launch import ew_meta as _ew_meta, ew_grid as _ew_grid
|
|
173
|
+
from nbxtensor.ops import tiling_sizes as _ts
|
|
174
|
+
import math
|
|
175
|
+
from nbxtensor.ops import elementwise_wrappers as _ew
|
|
176
|
+
from nbxtensor.ops import wrappers as _core
|
|
177
|
+
|
|
178
|
+
# gridDim.z architectural bound — 65535 on both CUDA and HIP (an API
|
|
179
|
+
# limit shared by every supported arch, not a tunable hardware param).
|
|
180
|
+
# Batched matmul launches put the batch on grid axis 2 and chunk past it.
|
|
181
|
+
_GRID_Z_MAX = 65535
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ===========================================================================
|
|
185
|
+
# MATMUL WRAPPERS
|
|
186
|
+
# ===========================================================================
|
|
187
|
+
|
|
188
|
+
def _matmul_out_dtype(a, M: int = 1, force_fp32: bool = False):
|
|
189
|
+
"""Output dtype for mm/bmm/addmm/mv/addmv.
|
|
190
|
+
|
|
191
|
+
Kernels accumulate in fp32. Three overlapping policies decide the
|
|
192
|
+
*store* dtype:
|
|
193
|
+
|
|
194
|
+
1. **Hardware gate — fp16 on pre-Ampere (no native bf16)**: force
|
|
195
|
+
fp32 output unconditionally for fp16 inputs. Mirrors what the
|
|
196
|
+
native DtypeEngine AMP already does on V100/Turing — fp16
|
|
197
|
+
accumulation over K can exceed 65,504 even when per-element
|
|
198
|
+
magnitudes look reasonable, and the store saturates to ±Inf
|
|
199
|
+
which cascades through residual adds (openaudio DualAR
|
|
200
|
+
Apr-2026, Qwen3-30B earlier). On Ampere+ / any bf16-capable
|
|
201
|
+
card this path is a no-op and the legacy M-gated rule applies.
|
|
202
|
+
The flag comes from `PrismProfile.has_native_bf16` via
|
|
203
|
+
`set_hardware_profile()` — ZERO V100 hardcode, data-driven
|
|
204
|
+
hardware capability. See the NeuroBrix universal-engine rule
|
|
205
|
+
in CLAUDE.md §5.
|
|
206
|
+
|
|
207
|
+
2. **M <= 4 (decode path)**: store fp32 even with bf16 inputs or
|
|
208
|
+
on bf16-capable hardware. Tiny tensors (1 row × N), negligible
|
|
209
|
+
memory cost, catches rare decode-loop drift.
|
|
210
|
+
|
|
211
|
+
3. **M > 4 (prefill / spatial path), bf16-capable hardware**:
|
|
212
|
+
store in input dtype. Keeps diffusion spatial mm (PixArt-Sigma
|
|
213
|
+
M=4096 × CFG=2) within VRAM budget. Prefill-magnitude
|
|
214
|
+
activations stay within the dtype range because a single
|
|
215
|
+
forward pass doesn't chain accumulations like a deep decode.
|
|
216
|
+
|
|
217
|
+
`force_fp32=True` overrides everything (used by bmm for diffusion
|
|
218
|
+
attention on all hardware).
|
|
219
|
+
|
|
220
|
+
**Phase 1.5 attempt (2026-05) — REVERTED.** A bypass of hardware
|
|
221
|
+
gate (1) when `activations_fp16_safe=True` was prototyped on the
|
|
222
|
+
intuition that fp16-safe activations would unlock HMMA-fp16 via
|
|
223
|
+
Triton. Empirical measurement on Sana 1024 (M=2048×2240) refuted
|
|
224
|
+
this: pure fp16 path was 6.52 ms vs 2.82 ms current path (2.3×
|
|
225
|
+
WORSE). Triton fp16×fp16 → fp32 accumulator on Volta does NOT
|
|
226
|
+
lower to HMMA-fp16 in this kernel — it falls back to fp32
|
|
227
|
+
emulation. cuBLAS reaches 0.295 ms via its own dedicated HMMA-fp16
|
|
228
|
+
code path that we cannot replicate by toggling a flag. The
|
|
229
|
+
force_fp32 hardware gate stays unconditional. Real speedup
|
|
230
|
+
requires either a HMMA-tuned Triton kernel OR adopting
|
|
231
|
+
FlagGems / similar — see Phase 1.5 follow-up.
|
|
232
|
+
"""
|
|
233
|
+
dt = a.nbx_dtype if hasattr(a, 'nbx_dtype') else a.dtype
|
|
234
|
+
# The decision is `launch_keys.matmul_out_dtype` — the one the derived census computes keys
|
|
235
|
+
# with (NBX_FORCE_FP32_ACCUM first, then the hardware gate, then the M / force_fp32 gate).
|
|
236
|
+
return _lk.matmul_out_dtype(dt, M, force_fp32, _core._NBX_HAS_NATIVE_BF16, NBX_FORCE_FP32_ACCUM,
|
|
237
|
+
contract_half=bool(_core._NBX_ACTIVATIONS_FP16_SAFE))
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _apply_matmul_epilogue(c, epilogue_code: int):
|
|
241
|
+
"""Unfused epilogue application for matmul fallback paths (decode
|
|
242
|
+
M <= 4 mv/addmv routing). Calls the SAME standalone wrappers the
|
|
243
|
+
unfused graph would dispatch, in the same order — byte-identical to
|
|
244
|
+
the unfused pair by construction, zero-risk on the decode path.
|
|
245
|
+
Codes mirror kernels/ops/matmul.py EPILOGUE_*."""
|
|
246
|
+
if epilogue_code == 0:
|
|
247
|
+
return c
|
|
248
|
+
if epilogue_code == 1:
|
|
249
|
+
return _ew.silu(c)
|
|
250
|
+
return _ew.gelu(c, approximate=('tanh' if epilogue_code == 3 else 'none'))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
_DEQUANT_GEMV_MIN_PROGRAMS = None # lazy: vendor-yml read once per process
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _dequant_gemv_block_n(N: int) -> int:
|
|
257
|
+
"""Occupancy-adapted BLOCK_N for the M=1 dequant-GEMV grid.
|
|
258
|
+
|
|
259
|
+
The tier's pinned parity BLOCK_N under-fills the device at small N
|
|
260
|
+
(the MoE router mv is N=128 -> a 2-program grid on 80 SMs, ~38 us
|
|
261
|
+
latency-bound). Halve BLOCK_N (pow2, floor 1) until the grid
|
|
262
|
+
reaches `block_sizes.dequant_gemv.min_programs` from the vendor
|
|
263
|
+
arch YAML (R7/R24). BLOCK_N only partitions OUTPUT columns; the
|
|
264
|
+
parity oracle runs the SAME adapted config, so fused == ref by
|
|
265
|
+
construction at every width. Key absent / 0 / any config error ->
|
|
266
|
+
the pinned BLOCK_N (prior behavior). Large-N grids that already
|
|
267
|
+
meet the target are untouched.
|
|
268
|
+
"""
|
|
269
|
+
from nbxtensor.ops.dequant_gemv import BLOCK_N
|
|
270
|
+
global _DEQUANT_GEMV_MIN_PROGRAMS
|
|
271
|
+
if _DEQUANT_GEMV_MIN_PROGRAMS is None:
|
|
272
|
+
target = 0
|
|
273
|
+
if os.environ.get("NBX_DGEMV_ADAPT", "1") == "0":
|
|
274
|
+
# Kill switch: pinned width everywhere (pre-adaptation
|
|
275
|
+
# behavior). Cached like the resolved target.
|
|
276
|
+
_DEQUANT_GEMV_MIN_PROGRAMS = 0
|
|
277
|
+
return BLOCK_N
|
|
278
|
+
try:
|
|
279
|
+
prof = _core.get_hardware_profile()
|
|
280
|
+
devices = getattr(prof, "devices", None) if prof else None
|
|
281
|
+
if devices:
|
|
282
|
+
from nbxtensor.backend.device_files import describe as get_vendor_config # the device, both halves
|
|
283
|
+
_brand = devices[0].brand
|
|
284
|
+
_vendor = getattr(_brand, "value", _brand)
|
|
285
|
+
cfg = get_vendor_config(_vendor, devices[0].architecture)
|
|
286
|
+
target = int(cfg.get("block_sizes", {})
|
|
287
|
+
.get("dequant_gemv", {})
|
|
288
|
+
.get("min_programs", 0))
|
|
289
|
+
except Exception:
|
|
290
|
+
target = 0
|
|
291
|
+
_DEQUANT_GEMV_MIN_PROGRAMS = max(0, target)
|
|
292
|
+
bn = BLOCK_N
|
|
293
|
+
target = _DEQUANT_GEMV_MIN_PROGRAMS
|
|
294
|
+
if target:
|
|
295
|
+
while bn > 1 and -(-N // bn) < target:
|
|
296
|
+
bn //= 2
|
|
297
|
+
return bn
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _mm_quantized(a, qt, _epilogue: int = 0):
|
|
301
|
+
"""mm where B is an int4-g128-asym QuantizedTensor (weight triplet).
|
|
302
|
+
|
|
303
|
+
The graph's `t(weight) -> mm` marks the triplet transposed — which
|
|
304
|
+
IS the packed layout's natural [K, N] orientation. Decode (M == 1)
|
|
305
|
+
runs the fused byte-gated dequant-GEMV with the family's pinned
|
|
306
|
+
parity config; prefill (M > 1) dequantizes to a transient dense
|
|
307
|
+
tensor via the family's standalone kernel (the byte oracle's own
|
|
308
|
+
dequant) and re-enters the normal mm path. Output dtype mirrors
|
|
309
|
+
the dense path (a's dtype).
|
|
310
|
+
"""
|
|
311
|
+
import triton as _tr
|
|
312
|
+
from nbxtensor.ops.dequant_gemv import (
|
|
313
|
+
BLOCK_K, BLOCK_N, NUM_WARPS, GROUP_SIZE, PACK,
|
|
314
|
+
dequant_gemv_int4_kernel, dequant_int4_kernel)
|
|
315
|
+
if not qt.transposed:
|
|
316
|
+
raise NotImplementedError(
|
|
317
|
+
"quantized mm without the graph-side transpose marker is "
|
|
318
|
+
"outside the v1 contract (the packed layout is [K, N]; "
|
|
319
|
+
"the traced linear always transposes) — named follow-up.")
|
|
320
|
+
a = _core._ensure_cuda(a)
|
|
321
|
+
K, N = qt.shape
|
|
322
|
+
M = a.shape[0] if a.ndim == 2 else 1
|
|
323
|
+
wq, sc, mn = qt.qweight, qt.scales, qt.qmins
|
|
324
|
+
if hasattr(a, '_device_idx') and a._device_idx != wq._device_idx:
|
|
325
|
+
a = _core._transfer_to_device(a, wq._device_idx)
|
|
326
|
+
set_device(wq)
|
|
327
|
+
|
|
328
|
+
if a.ndim == 2 and M == 1 or a.ndim == 1:
|
|
329
|
+
x = a.contiguous().view(-1)
|
|
330
|
+
out32 = NBXTensor.empty((N,), dtype=NBXDtype.float32,
|
|
331
|
+
device=a.device)
|
|
332
|
+
bn = _dequant_gemv_block_n(N)
|
|
333
|
+
dequant_gemv_int4_kernel[(_tr.cdiv(N, bn),)](
|
|
334
|
+
x, wq, sc, mn, out32, K, N,
|
|
335
|
+
wq.stride(0), wq.stride(1), sc.stride(0), sc.stride(1),
|
|
336
|
+
BLOCK_K_C=BLOCK_K, BLOCK_N_C=bn,
|
|
337
|
+
GROUP_C=GROUP_SIZE, PACK_C=PACK,
|
|
338
|
+
num_warps=NUM_WARPS, num_stages=2)
|
|
339
|
+
out = out32.to(a.nbx_dtype) if a.nbx_dtype != NBXDtype.float32 \
|
|
340
|
+
else out32
|
|
341
|
+
return out.view(1, N) if a.ndim == 2 else out
|
|
342
|
+
|
|
343
|
+
# Prefill: transient dense via the family's standalone dequant.
|
|
344
|
+
dense32 = NBXTensor.empty((K, N), dtype=NBXDtype.float32,
|
|
345
|
+
device=a.device)
|
|
346
|
+
dequant_int4_kernel[(_tr.cdiv(K, BLOCK_K), _tr.cdiv(N, BLOCK_N))](
|
|
347
|
+
wq, sc, mn, dense32, K, N,
|
|
348
|
+
wq.stride(0), wq.stride(1), sc.stride(0), sc.stride(1),
|
|
349
|
+
dense32.stride(0), dense32.stride(1),
|
|
350
|
+
BLOCK_K_C=BLOCK_K, BLOCK_N_C=BLOCK_N,
|
|
351
|
+
GROUP_C=GROUP_SIZE, PACK_C=PACK,
|
|
352
|
+
num_warps=NUM_WARPS, num_stages=2)
|
|
353
|
+
dense = dense32.to(a.nbx_dtype) if a.nbx_dtype != NBXDtype.float32 \
|
|
354
|
+
else dense32
|
|
355
|
+
return mm(a, dense, _epilogue)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _gemm_unit_tile(a, b, M: int, N: int):
|
|
359
|
+
"""The matrix unit's tile for this GEMM, with how it carries each operand (`split_a` / `split_b`: the
|
|
360
|
+
DtypeEngine's representation, `launch_keys.matrix_unit_operands`), else None. Two native operands take a
|
|
361
|
+
`matrix_unit.mm` row (their products are exact in the fp32 accumulator, as on the tl.dot path); a split one
|
|
362
|
+
takes a `matrix_unit.fp32_split.mm` row (its three terms hold three accumulators)."""
|
|
363
|
+
reps = _lk.matrix_unit_operands(a.nbx_dtype, b.nbx_dtype, M) # the census keys with the same function
|
|
364
|
+
if reps is None:
|
|
365
|
+
return None
|
|
366
|
+
mu = _matrix_unit()
|
|
367
|
+
split = "split" in reps
|
|
368
|
+
tile = _matrix_unit_mm_tile(M, N, mu["fp32_split"] if split else mu)
|
|
369
|
+
tile["split_a"], tile["split_b"] = reps[0] == "split", reps[1] == "split"
|
|
370
|
+
return tile
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _gemm_route(tuner, args, kwargs, unit_tile):
|
|
374
|
+
"""The tile a GEMM the matrix unit can take (`unit_tile`, `_gemm_unit_tile`) runs at on the unit, or None for its
|
|
375
|
+
FMA kernel: the certificate of the FMA launch's key decides, which measured both routes on this card's class and
|
|
376
|
+
keeps the faster (`autotune_certified.unit_choice`) — never a threshold. A census shadow and the certifier's sweep
|
|
377
|
+
take the FMA kernel (its key is recorded, its configurations measured, the unit's tiles beside them); a
|
|
378
|
+
confirmation run with no certificate takes it too, and the launch refuses the missing key by name; any other run
|
|
379
|
+
with no certificate takes the unit at the profile's tile, as the vendor file declares it."""
|
|
380
|
+
if unit_tile is None:
|
|
381
|
+
return None
|
|
382
|
+
from nbxtensor.ops import autotune_certified as _cert
|
|
383
|
+
from nbxtensor.ops.launch_observer import launch_observer
|
|
384
|
+
if launch_observer().active() or _cert.certifying():
|
|
385
|
+
return None
|
|
386
|
+
chosen = _cert.unit_choice(tuner, args, kwargs)
|
|
387
|
+
if chosen is None:
|
|
388
|
+
return None if _cert.certified_only() else unit_tile
|
|
389
|
+
kind, tile = chosen
|
|
390
|
+
if kind == "fma":
|
|
391
|
+
return None
|
|
392
|
+
return {**tile, "split_a": unit_tile["split_a"], "split_b": unit_tile["split_b"]}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def matrix_unit_runs(tuner, args, kwargs):
|
|
396
|
+
"""(tile, run) for each matrix-unit tile the hardware profile declares for this `matmul_kernel` / `addmm_kernel`
|
|
397
|
+
launch (its arguments as the autotuner receives them), where the unit can take the GEMM
|
|
398
|
+
(`launch_keys.matrix_unit_operands`): the rows of `matrix_unit.mm`, or of `fp32_split.mm` for an operand carried
|
|
399
|
+
as hi + lo. Each run writes the launch's own output on the unit, so a certifier can prove it against the launch's
|
|
400
|
+
oracle and time it beside the FMA configurations (the certificate then names the faster). Empty otherwise."""
|
|
401
|
+
from nbxtensor.ops import autotune_certified as _cert
|
|
402
|
+
if getattr(getattr(tuner, "base_fn", None), "__name__", None) not in ("matmul_kernel", "addmm_kernel"):
|
|
403
|
+
return []
|
|
404
|
+
named = dict(zip(tuner.arg_names, args))
|
|
405
|
+
a, b, c = named["a_ptr"], named["b_ptr"], named["c_ptr"]
|
|
406
|
+
reps = _lk.matrix_unit_operands(a.nbx_dtype, b.nbx_dtype, int(named["M"]))
|
|
407
|
+
if reps is None:
|
|
408
|
+
return []
|
|
409
|
+
mu = _matrix_unit()
|
|
410
|
+
bias = named.get("bias_ptr")
|
|
411
|
+
alpha, beta = float(named.get("alpha", 1.0)), float(named.get("beta", 0.0))
|
|
412
|
+
promote_bias, epilogue = bool(kwargs.get("PROMOTE_BIAS", False)), int(kwargs.get("EPILOGUE", 0))
|
|
413
|
+
out = []
|
|
414
|
+
for row in (mu["fp32_split"] if "split" in reps else mu)["mm"]:
|
|
415
|
+
tile = {f: int(row[f]) for f in _cert._UNIT_TILE_FIELDS}
|
|
416
|
+
run_tile = {**tile, "split_a": reps[0] == "split", "split_b": reps[1] == "split"}
|
|
417
|
+
out.append((tile, (lambda t=run_tile: _gemm_m8n8k4(a, b, bias, c, alpha, beta, promote_bias, epilogue, t))))
|
|
418
|
+
return out
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _gemm_m8n8k4(a, b, bias, c, alpha, beta, promote_bias, epilogue, tile, bias_strides=None):
|
|
422
|
+
"""C = A @ B (+ beta * bias, alpha scaling) on the m8n8k4 matrix unit (ops/matmul_m8n8k4.py). 2-D or batched
|
|
423
|
+
3-D operands: `a` row-major per batch, `b` walked by its strides, `bias` None or broadcast by `bias_strides`
|
|
424
|
+
(batch, row, column; default a [N] / [1, N] row). The batch runs on grid axis 1, chunked at _GRID_Z_MAX."""
|
|
425
|
+
from nbxtensor.ops.matmul_m8n8k4 import matmul_m8n8k4_kernel, mm_layouts
|
|
426
|
+
batched = a.ndim == 3
|
|
427
|
+
Z = a.shape[0] if batched else 1
|
|
428
|
+
M, K = a.shape[-2:]
|
|
429
|
+
N = b.shape[-1]
|
|
430
|
+
sa = (a.stride(0) if batched else 0, a.stride(-2), a.stride(-1))
|
|
431
|
+
sb = (b.stride(0) if batched else 0, b.stride(-2), b.stride(-1))
|
|
432
|
+
sc = (c.stride(0) if batched else 0, c.stride(-2), c.stride(-1))
|
|
433
|
+
sx = (0, 0, bias.stride(-1)) if bias is not None and bias_strides is None else (bias_strides or (0, 0, 0))
|
|
434
|
+
MB, NB = tile["block_m"] // 16, tile["block_n"] // 16
|
|
435
|
+
b_k_contig = sb[1] == 1 and N > 1
|
|
436
|
+
lay = mm_layouts(MB, NB, tile["warps_m"], tile["warps_n"], tile["block_k"], b_k_contig)
|
|
437
|
+
from nbxtensor.dtype.operands import matrix_unit_split_scale # the library's operand rules
|
|
438
|
+
hi_exp, lo_shift = matrix_unit_split_scale(_matrix_unit())
|
|
439
|
+
set_device(a)
|
|
440
|
+
for z0 in range(0, Z, _GRID_Z_MAX):
|
|
441
|
+
zb = min(_GRID_Z_MAX, Z - z0)
|
|
442
|
+
grid = (triton.cdiv(M, tile["block_m"]) * triton.cdiv(N, tile["block_n"]), zb)
|
|
443
|
+
x = c if bias is None else bias
|
|
444
|
+
matmul_m8n8k4_kernel[grid](
|
|
445
|
+
a if z0 == 0 else a.narrow(0, z0, zb), b if z0 == 0 else b.narrow(0, z0, zb),
|
|
446
|
+
x if z0 == 0 or sx[0] == 0 else x.narrow(0, z0, zb), c if z0 == 0 else c.narrow(0, z0, zb),
|
|
447
|
+
M, N, K, *sa, *sb, *sc, *sx,
|
|
448
|
+
float(alpha), float(beta),
|
|
449
|
+
HAS_BIAS=bias is not None, PROMOTE_BIAS=bool(promote_bias), EPILOGUE=int(epilogue),
|
|
450
|
+
B_K_CONTIG=b_k_contig, SPLIT_A=tile["split_a"], SPLIT_B=tile["split_b"], HI_EXP=hi_exp,
|
|
451
|
+
LO_SHIFT=lo_shift, MB=MB, NB=NB, BK=tile["block_k"], GROUP_M=tile["group_m"],
|
|
452
|
+
num_warps=tile["warps_m"] * tile["warps_n"], **lay)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def mm(a, b, _epilogue: int = 0) :
|
|
456
|
+
"""Matrix multiplication: C = A @ B.
|
|
457
|
+
Kernel accumulates in fp32 (hardware). Output matches input dtype.
|
|
458
|
+
DtypeEngine handles fp16/bf16 overflow protection externally.
|
|
459
|
+
|
|
460
|
+
`_epilogue` (fusion_vertical pass): fused elementwise epilogue code
|
|
461
|
+
(0 none, 1 silu, 2 gelu exact, 3 gelu tanh) executed in-kernel with
|
|
462
|
+
per-stage rounding emulation — see kernels/ops/matmul.py. The
|
|
463
|
+
M <= 4 decode path stays UNFUSED (mv routing + standalone epilogue
|
|
464
|
+
wrapper) so decode bytes are untouched by construction.
|
|
465
|
+
|
|
466
|
+
Small-M fast path (M <= 4): route to mv_wrapper per-row. matmul_kernel
|
|
467
|
+
tiles on BLOCK_M=64 and wastes 63/64 threads when M=1 (decode). mv_kernel
|
|
468
|
+
parallelises on N only and is ~10–15× faster in that regime. Universal:
|
|
469
|
+
works for LLM decode (M=1), short-sequence audio, small-batch inference.
|
|
470
|
+
For M > 4, stays on matmul_kernel which is optimal for larger GEMM
|
|
471
|
+
(diffusion spatial mm with M in the thousands).
|
|
472
|
+
"""
|
|
473
|
+
from nbxtensor.quant.quantized_tensor import QuantizedTensor
|
|
474
|
+
if isinstance(b, QuantizedTensor):
|
|
475
|
+
return _mm_quantized(a, b, _epilogue)
|
|
476
|
+
a = _core._ensure_cuda(a)
|
|
477
|
+
b = _core._ensure_cuda(b)
|
|
478
|
+
# Multi-device: align b to a's device
|
|
479
|
+
if hasattr(a, '_device_idx') and hasattr(b, '_device_idx') and a._device_idx != b._device_idx:
|
|
480
|
+
b = _core._transfer_to_device(b, a._device_idx)
|
|
481
|
+
|
|
482
|
+
# Hardware-gated fp16 overflow protection. On GPUs without native bf16
|
|
483
|
+
# (Volta/Turing/older), fp16 inputs whose upstream producers had large
|
|
484
|
+
# magnitudes get saturated to ±Inf when a graph-level _to_copy cast
|
|
485
|
+
# them back to fp16 — the next mm then propagates Inf through HMMA →
|
|
486
|
+
# NaN cascade. Mirror the native DtypeEngine AMP path: upcast the
|
|
487
|
+
# ACTIVATION to fp32. On bf16-capable hardware this branch is a no-op.
|
|
488
|
+
# NBXTensor.dtype returns triton.language.dtype (for kernel dispatch);
|
|
489
|
+
# NBXTensor.nbx_dtype returns the NBXDtype enum used by these guards.
|
|
490
|
+
#
|
|
491
|
+
# NOTE (Phase 1.5 attempt, reverted): an activations_fp16_safe opt-in
|
|
492
|
+
# bypass was prototyped but empirically slower on Sana 1024 (6.5 vs
|
|
493
|
+
# 2.8 ms — Triton fp16×fp16 does NOT lower to HMMA on Volta in this
|
|
494
|
+
# kernel; cuBLAS reaches 0.295 ms via dedicated HMMA path we cannot
|
|
495
|
+
# match by flag-flipping). Real speedup needs a HMMA-tuned Triton
|
|
496
|
+
# kernel or FlagGems adoption — see Phase 1.5 follow-up.
|
|
497
|
+
# The operand decisions are `launch_keys.mm_dtypes` — the function the derived census keys
|
|
498
|
+
# with; the wrapper only performs the casts it names. NBX_FORCE_FP32_ACCUM (a diagnostic)
|
|
499
|
+
# widens both half inputs first.
|
|
500
|
+
#
|
|
501
|
+
# The fp16 activation is NOT materialised as fp32 any more: the kernels widen it in
|
|
502
|
+
# registers — matmul_kernel through PROMOTE_A, the GEMV kernels on every load — the same
|
|
503
|
+
# numbers the copy produced (an exact widening), without the copy per matmul. The store
|
|
504
|
+
# dtype is decided by _matmul_out_dtype from the hardware gate, as before (fp32 here).
|
|
505
|
+
a_to, b_to, promote_a, promote_b = _lk.mm_dtypes(a.nbx_dtype, b.nbx_dtype,
|
|
506
|
+
_core._NBX_HAS_NATIVE_BF16, NBX_FORCE_FP32_ACCUM)
|
|
507
|
+
if a.nbx_dtype != a_to:
|
|
508
|
+
a = a.to(a_to)
|
|
509
|
+
if b.nbx_dtype != b_to:
|
|
510
|
+
b = b.to(b_to)
|
|
511
|
+
|
|
512
|
+
# Dtype alignment (decided in `launch_keys.mm_dtypes`). Three situations:
|
|
513
|
+
# 1. Same dtype → no-op.
|
|
514
|
+
# 2. fp32 act × fp16 weight on pre-Ampere → this is the common case
|
|
515
|
+
# (the activation widened in-kernel). Keep the weight fp16 in memory; the kernel
|
|
516
|
+
# promotes the b tile to fp32 inline via PROMOTE_B. No heap alloc.
|
|
517
|
+
# 3. Anything else (fp32 × bf16, bf16 × fp16, etc.) → widen to the
|
|
518
|
+
# common dtype. Rare; typically a downstream force_fp32 bmm feeding
|
|
519
|
+
# the next matmul.
|
|
520
|
+
# A NARROW WEIGHT IS PROMOTED IN THE KERNEL, NOT MATERIALISED.
|
|
521
|
+
#
|
|
522
|
+
# `PROMOTE_B` makes the kernel do `b = b.to(a.dtype)` on the loaded tile —
|
|
523
|
+
# it is dtype-agnostic (matmul.py:243) and has always been able to take
|
|
524
|
+
# bf16. The `not _NBX_HAS_NATIVE_BF16` guard is a pre-Ampere CUDA
|
|
525
|
+
# condition, and it excluded every machine that HAS native bf16 from a path
|
|
526
|
+
# that has nothing to do with bf16 arithmetic: here `a_eff` is fp32, so the
|
|
527
|
+
# math is fp32 either way and the only question is whether the weight is
|
|
528
|
+
# widened in memory first.
|
|
529
|
+
#
|
|
530
|
+
# It was being widened, on EVERY call, for a weight that never changes.
|
|
531
|
+
# Measured on an M4 Pro (TinyLlama-1.1B, 120 tokens, 2026-09-18):
|
|
532
|
+
# `strided_copy_nd_kernel` was 1408 calls and 11.86 s of a 19.74 s decode —
|
|
533
|
+
# 60% — and its four dominant shapes are weight-sized with transposing
|
|
534
|
+
# strides, e.g. shape (2048, 5632) src (1, 2048) -> dst (5632, 1) at
|
|
535
|
+
# ~5.5 GB/s against the 80.97 GB/s a contiguous copy reaches. Every one came
|
|
536
|
+
# from this line via `b.to(widest)`.
|
|
537
|
+
#
|
|
538
|
+
# bf16 -> fp32 is lossless, so promoting per tile yields the same fp32
|
|
539
|
+
# values the materialised cast did; the outputs are expected to be
|
|
540
|
+
# bit-identical and that is checked, not assumed.
|
|
541
|
+
M, K = a.shape
|
|
542
|
+
K2, N = b.shape
|
|
543
|
+
assert K == K2, f"Incompatible dimensions: {K} vs {K2}"
|
|
544
|
+
|
|
545
|
+
# === TEMP DIAGNOSTIC: record M,N,K per call ===
|
|
546
|
+
import os as _os
|
|
547
|
+
if _os.environ.get("NBX_MM_SHAPES") == "1":
|
|
548
|
+
import collections, atexit
|
|
549
|
+
if "_rec" not in mm.__dict__:
|
|
550
|
+
mm.__dict__["_rec"] = collections.Counter()
|
|
551
|
+
mm.__dict__["_call_idx"] = [0]
|
|
552
|
+
|
|
553
|
+
def _dump_shapes():
|
|
554
|
+
rec = mm.__dict__["_rec"]
|
|
555
|
+
print("\n[NBX_MM_SHAPES] unique (M,N,K) × count:")
|
|
556
|
+
for (m_, n_, k_), c in sorted(rec.items(),
|
|
557
|
+
key=lambda x: -x[1]):
|
|
558
|
+
print(f" M={m_:5d} N={n_:5d} K={k_:5d} × {c}")
|
|
559
|
+
print(f"[NBX_MM_SHAPES] total calls: {sum(rec.values())}")
|
|
560
|
+
atexit.register(_dump_shapes)
|
|
561
|
+
mm.__dict__["_rec"][(M, N, K)] += 1
|
|
562
|
+
# ================================================
|
|
563
|
+
|
|
564
|
+
# Small-M GEMV path. Do NOT call b.contiguous() on the full weight:
|
|
565
|
+
# if b is a pre-transposed stride-view (from _eliminate_weight_transpose_ops),
|
|
566
|
+
# b.t() restores the original contiguous layout for free. Calling
|
|
567
|
+
# b.contiguous() first would copy the entire weight — defeating the point.
|
|
568
|
+
if M <= 4:
|
|
569
|
+
set_device(a)
|
|
570
|
+
a = a.contiguous()
|
|
571
|
+
bt = b.t() # (N, K); contiguous if b came from pre-transpose
|
|
572
|
+
dtype_out = _matmul_out_dtype(a, M)
|
|
573
|
+
dev_str = (f"cuda:{a._device_idx}" if hasattr(a, '_device_idx')
|
|
574
|
+
else 'cuda')
|
|
575
|
+
c = NBXTensor.empty((M, N), device=dev_str, dtype=dtype_out)
|
|
576
|
+
for i in range(M):
|
|
577
|
+
c[i] = mv_wrapper(bt, a[i])
|
|
578
|
+
return _apply_matmul_epilogue(c, _epilogue)
|
|
579
|
+
|
|
580
|
+
a = a.contiguous()
|
|
581
|
+
# The weight is walked by its own strides: matmul_kernel receives (stride_bk, stride_bn)
|
|
582
|
+
# and loads the B tile through them, so a pre-transposed weight — the (K, N) stride view of
|
|
583
|
+
# a (N, K) row-major buffer, stride_bk == 1 — is read in place. Materialising it here copied
|
|
584
|
+
# the whole model once per prefill (TinyLlama: 154 weights, 2.2 GB, per request — the copy
|
|
585
|
+
# census of 2026-09-07). Only a broadcast (a zero stride) is materialised: the kernel's
|
|
586
|
+
# integer analysis assumes every stride positive.
|
|
587
|
+
if any(st == 0 for st in b.stride()):
|
|
588
|
+
b = b.contiguous()
|
|
589
|
+
out_dtype = _matmul_out_dtype(a, M, force_fp32=promote_a)
|
|
590
|
+
c = NBXTensor.empty((M, N), device=f"cuda:{a._device_idx}" if hasattr(a, '_device_idx') else 'cuda',
|
|
591
|
+
dtype=out_dtype)
|
|
592
|
+
# IEEE mode: force strict fp32 tl.dot on pre-Ampere when we've promoted
|
|
593
|
+
# inputs to fp32 for overflow protection. Otherwise tl.dot silently
|
|
594
|
+
# casts fp32 → fp16 HMMA → saturates at ±65504 → NaN/Inf cascade.
|
|
595
|
+
ieee = (not _core._NBX_HAS_NATIVE_BF16) and (out_dtype == NBXDtype.float32)
|
|
596
|
+
# Phase 1.5 autotune: BLOCK_*/GROUP_M/num_warps/num_stages chosen
|
|
597
|
+
# adaptively per (M, N, K, IEEE_PRECISION, PROMOTE_B). Grid uses
|
|
598
|
+
# META lambda so autotune-selected blocks drive the launch shape.
|
|
599
|
+
set_device(a)
|
|
600
|
+
# ROW BANDING above 2^31 OUTPUT ELEMENTS. A launch whose C holds more than 2^31 elements
|
|
601
|
+
# writes nothing past row `2**31 // N` on the Metal backend: the offsets are promoted to
|
|
602
|
+
# int64 in the kernel (matmul.py:257) and CUDA is exact, but the Metal lowering does not
|
|
603
|
+
# honour it (2026-09-23; tests/unit/kernels/test_a_gemm_beyond_two_billion_elements.py).
|
|
604
|
+
# Three kernel-side spellings were tried and none helped, so the launch is split instead:
|
|
605
|
+
# each band's own C is under the boundary, and the arithmetic per band is untouched.
|
|
606
|
+
#
|
|
607
|
+
# The AUTOTUNE KEY IS PRESERVED. `M_BUCKET` is a separate argument from `M` and the key is
|
|
608
|
+
# keyed on the bucket, so every band is served the configuration certified for the whole
|
|
609
|
+
# shape — banding changes how many launches happen, never which entry answers for them.
|
|
610
|
+
bands = [(0, M)]
|
|
611
|
+
if N > 0 and M * N > _NBX_MM_MAX_OUTPUT_ELEMS:
|
|
612
|
+
per = max(1, _NBX_MM_MAX_OUTPUT_ELEMS // N)
|
|
613
|
+
bands = [(r, min(r + per, M)) for r in range(0, M, per)]
|
|
614
|
+
m_bucket = _bucket_of("M", M)
|
|
615
|
+
_mu_tile = _gemm_unit_tile(a, b, M, N)
|
|
616
|
+
for r0, r1 in bands:
|
|
617
|
+
rows = r1 - r0
|
|
618
|
+
a_b = a[r0:r1] if len(bands) > 1 else a
|
|
619
|
+
c_b = c[r0:r1] if len(bands) > 1 else c
|
|
620
|
+
args = (a_b, b, c_b, rows, N, K, m_bucket, a_b.stride(0), a_b.stride(1), b.stride(0), b.stride(1),
|
|
621
|
+
c_b.stride(0), c_b.stride(1))
|
|
622
|
+
kwargs = dict(IEEE_PRECISION=ieee, PROMOTE_B=promote_b, PROMOTE_A=promote_a, EPILOGUE=_epilogue)
|
|
623
|
+
tile = _gemm_route(matmul_kernel, args, kwargs, _mu_tile)
|
|
624
|
+
if tile is not None:
|
|
625
|
+
_gemm_m8n8k4(a_b, b, None, c_b, 1.0, 0.0, False, _epilogue, tile)
|
|
626
|
+
continue
|
|
627
|
+
grid = (lambda META, _m=rows: (triton.cdiv(_m, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),))
|
|
628
|
+
_core._autotune_headroom_guard(matmul_kernel[grid])(*args, **kwargs)
|
|
629
|
+
return c
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def bmm(a, b, allow_strided_b: bool = False) :
|
|
633
|
+
"""Batched matrix multiplication: C[i] = A[i] @ B[i].
|
|
634
|
+
|
|
635
|
+
`allow_strided_b` lets the caller hand in a non-contiguous B and have
|
|
636
|
+
the kernel walk it by its own strides. `baddbmm_kernel` already
|
|
637
|
+
receives all three of B's strides; the unconditional `.contiguous()`
|
|
638
|
+
below simply threw that generality away. Off by default — a badly
|
|
639
|
+
strided operand can cost more in uncoalesced loads than the copy it
|
|
640
|
+
saves, so it is opt-in per call site with its measurement, never a
|
|
641
|
+
blanket change. See `_math_attention`, the first caller.
|
|
642
|
+
Kernel accumulates in fp32 (hardware). Output matches input dtype.
|
|
643
|
+
DtypeEngine handles overflow protection externally.
|
|
644
|
+
|
|
645
|
+
One batched 3D-grid launch via `baddbmm_kernel` (HAS_BIAS=False) for
|
|
646
|
+
every M — P-KERNEL-LAUNCH-HYGIENE B1. The former per-batch
|
|
647
|
+
matmul_kernel loop (prefill) and per-batch × per-row mv loop (decode
|
|
648
|
+
M<=4) issued B (resp. B·M) launches per call; on the deterministic
|
|
649
|
+
decode path (`_math_attention`, batch=B·H, M=1) that was thousands of
|
|
650
|
+
launches per generated token, pure launch-bound. The batched kernel
|
|
651
|
+
body is a line-for-line mirror of matmul_kernel, so results are
|
|
652
|
+
value-identical to the former prefill loop under the same autotune
|
|
653
|
+
config; the decode path leaves the mv reduction order for tl.dot
|
|
654
|
+
tiles (fp reassociation-level difference only — products are exact in
|
|
655
|
+
the fp32 accumulator for half inputs).
|
|
656
|
+
"""
|
|
657
|
+
a = _core._ensure_cuda(a)
|
|
658
|
+
b = _core._ensure_cuda(b)
|
|
659
|
+
if hasattr(a, '_device_idx') and hasattr(b, '_device_idx') and a._device_idx != b._device_idx:
|
|
660
|
+
b = _core._transfer_to_device(b, a._device_idx)
|
|
661
|
+
|
|
662
|
+
# The matrix unit takes two operands of its operand dtype as they are in memory: the widening below exists
|
|
663
|
+
# because the tl.dot kernel has no PROMOTE_A, and an fp16 value is exact in fp32 with fp16 x fp16 products
|
|
664
|
+
# exact in the fp32 accumulator, so the unit computes the same sums (the store dtype is unchanged). Rows that
|
|
665
|
+
# do not fill the unit's smallest tile (decode) keep the batched kernel (launch_keys.matrix_unit_operands).
|
|
666
|
+
_mu_tile = _gemm_unit_tile(a, b, a.shape[1], b.shape[2])
|
|
667
|
+
if _mu_tile is not None:
|
|
668
|
+
a = a.contiguous()
|
|
669
|
+
if not allow_strided_b:
|
|
670
|
+
b = b.contiguous()
|
|
671
|
+
c = NBXTensor.empty((a.shape[0], a.shape[1], b.shape[2]), device=a.device,
|
|
672
|
+
dtype=_matmul_out_dtype(a, a.shape[1], force_fp32=True))
|
|
673
|
+
_gemm_m8n8k4(a, b, None, c, 1.0, 0.0, False, 0, _mu_tile)
|
|
674
|
+
return c
|
|
675
|
+
|
|
676
|
+
# Hardware-gated fp16→fp32 input upcast — same rationale as mm().
|
|
677
|
+
# Use nbx_dtype for guard comparisons; .dtype returns triton.language.dtype.
|
|
678
|
+
# The operand decisions are `launch_keys.bmm_dtypes` — the function the derived census keys
|
|
679
|
+
# with (NBX_FORCE_FP32_ACCUM first; an fp16 activation widened in memory on hardware without
|
|
680
|
+
# native bf16 — bmm has no PROMOTE_A; an fp16 weight under fp32 widened in the kernel there;
|
|
681
|
+
# any other mismatch widened to the widest). The wrapper only casts.
|
|
682
|
+
a_to, b_to, promote_b = _lk.bmm_dtypes(a.nbx_dtype, b.nbx_dtype,
|
|
683
|
+
_core._NBX_HAS_NATIVE_BF16, NBX_FORCE_FP32_ACCUM)
|
|
684
|
+
if a.nbx_dtype != a_to:
|
|
685
|
+
a = a.to(a_to)
|
|
686
|
+
if b.nbx_dtype != b_to:
|
|
687
|
+
b = b.to(b_to)
|
|
688
|
+
|
|
689
|
+
B, M, K = a.shape
|
|
690
|
+
B2, K2, N = b.shape
|
|
691
|
+
assert B == B2 and K == K2, f"bmm shape mismatch: {tuple(a.shape)} @ {tuple(b.shape)}"
|
|
692
|
+
|
|
693
|
+
a = a.contiguous()
|
|
694
|
+
if not allow_strided_b:
|
|
695
|
+
b = b.contiguous()
|
|
696
|
+
out_dtype = _matmul_out_dtype(a, M, force_fp32=True)
|
|
697
|
+
c = NBXTensor.empty((B, M, N), device=a.device, dtype=out_dtype)
|
|
698
|
+
ieee = (not _core._NBX_HAS_NATIVE_BF16) and (out_dtype == NBXDtype.float32)
|
|
699
|
+
|
|
700
|
+
# Match mm() — sync Triton driver to a's device immediately before launch.
|
|
701
|
+
# NBXTensor.empty(...) above can cudaSetDevice without updating Triton's
|
|
702
|
+
# driver active device, and dtype alignment paths may also have touched
|
|
703
|
+
# it. Without this, batched kernels on non-zero devices hit
|
|
704
|
+
# "Pointer argument (at 0) cannot be accessed from Triton".
|
|
705
|
+
set_device(a)
|
|
706
|
+
# Batched launch — grid axis 2 walks the batch inside the kernel
|
|
707
|
+
# (baddbmm_kernel HAS_BIAS=False). `c` doubles as the (dead) bias
|
|
708
|
+
# pointer: with HAS_BIAS=False the bias load is compiled out, so no
|
|
709
|
+
# uninitialized read; alpha/beta are likewise unused. gridDim.z caps
|
|
710
|
+
# at _GRID_Z_MAX: matmul_wrapper collapses ALL leading dims into B, so
|
|
711
|
+
# an extreme collapsed batch chunks into z-sized launches (one launch
|
|
712
|
+
# per 65535 batches — the loop body runs exactly once below the cap,
|
|
713
|
+
# with no narrow() on that fast path).
|
|
714
|
+
for z0 in range(0, B, _GRID_Z_MAX):
|
|
715
|
+
zb = min(_GRID_Z_MAX, B - z0)
|
|
716
|
+
a_z = a if zb == B else a.narrow(0, z0, zb)
|
|
717
|
+
b_z = b if zb == B else b.narrow(0, z0, zb)
|
|
718
|
+
c_z = c if zb == B else c.narrow(0, z0, zb)
|
|
719
|
+
grid = lambda META, _zb=zb: (
|
|
720
|
+
triton.cdiv(M, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),
|
|
721
|
+
1,
|
|
722
|
+
_zb,
|
|
723
|
+
)
|
|
724
|
+
_core._autotune_headroom_guard(baddbmm_kernel[grid])(
|
|
725
|
+
a_z, b_z, c_z, c_z,
|
|
726
|
+
1.0, 0.0,
|
|
727
|
+
M, N, K, _bucket_of("M", M), _bucket_of("N", N), _bucket_of("K", K),
|
|
728
|
+
a.stride(0), a.stride(1), a.stride(2),
|
|
729
|
+
b.stride(0), b.stride(1), b.stride(2),
|
|
730
|
+
c.stride(0), c.stride(1), c.stride(2),
|
|
731
|
+
0, 0, 0,
|
|
732
|
+
IEEE_PRECISION=ieee,
|
|
733
|
+
PROMOTE_B=promote_b,
|
|
734
|
+
HAS_BIAS=False,
|
|
735
|
+
)
|
|
736
|
+
return c
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def matmul_wrapper(a, b):
|
|
740
|
+
"""General matmul dispatcher: routes to mm, bmm, or mv based on input dims.
|
|
741
|
+
|
|
742
|
+
Matches torch.matmul semantics:
|
|
743
|
+
- 2D × 2D → mm
|
|
744
|
+
- 3D × 3D → bmm
|
|
745
|
+
- 2D × 1D → mv
|
|
746
|
+
- ND × 2D → reshape to batched mm
|
|
747
|
+
"""
|
|
748
|
+
route = _lk.matmul_route(a.ndim, b.ndim) # the route the derived census keys with
|
|
749
|
+
if route == "mm":
|
|
750
|
+
return mm(a, b)
|
|
751
|
+
if route == "bmm":
|
|
752
|
+
return bmm(a, b)
|
|
753
|
+
if route == "mv":
|
|
754
|
+
return mv_wrapper(a, b)
|
|
755
|
+
if route == "batched":
|
|
756
|
+
# Batched: reshape a to (batch, M, K), mm each, reshape back
|
|
757
|
+
orig_shape = a.shape
|
|
758
|
+
M, K = orig_shape[-2], orig_shape[-1]
|
|
759
|
+
batch = a.numel() // (M * K)
|
|
760
|
+
a_3d = a.contiguous().view(batch, M, K)
|
|
761
|
+
result = bmm(a_3d, b.unsqueeze(0).expand(batch, K, b.shape[1]))
|
|
762
|
+
return result.view(*orig_shape[:-1], b.shape[1])
|
|
763
|
+
if route == "general":
|
|
764
|
+
# General batched matmul. bmm is strictly 3D, so collapse the leading
|
|
765
|
+
# batch dims into one, bmm, then restore the batch shape. Passing raw 4-D
|
|
766
|
+
# tensors straight to bmm unpacked a 3-tuple → "too many values".
|
|
767
|
+
a_c = a.contiguous(); b_c = b.contiguous()
|
|
768
|
+
M, K = a_c.shape[-2], a_c.shape[-1]
|
|
769
|
+
Kb, N = b_c.shape[-2], b_c.shape[-1]
|
|
770
|
+
batch_a = a_c.numel() // (M * K)
|
|
771
|
+
batch_b = b_c.numel() // (Kb * N)
|
|
772
|
+
a_3d = a_c.view(batch_a, M, K)
|
|
773
|
+
b_3d = b_c.view(batch_b, Kb, N)
|
|
774
|
+
lead = list(a_c.shape[:-2])
|
|
775
|
+
if batch_b == 1 and batch_a != 1:
|
|
776
|
+
b_3d = b_3d.expand(batch_a, Kb, N)
|
|
777
|
+
elif batch_a == 1 and batch_b != 1:
|
|
778
|
+
a_3d = a_3d.expand(batch_b, M, K)
|
|
779
|
+
lead = list(b_c.shape[:-2])
|
|
780
|
+
result = bmm(a_3d, b_3d)
|
|
781
|
+
return result.view(*lead, M, N)
|
|
782
|
+
raise RuntimeError(f"matmul: unsupported shapes {a.shape} × {b.shape}")
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def linear_wrapper(input, weight, bias=None):
|
|
786
|
+
"""aten::linear — y = input @ weight.T + bias.
|
|
787
|
+
|
|
788
|
+
Signature matches torch.nn.functional.linear:
|
|
789
|
+
input : (..., in_features)
|
|
790
|
+
weight: (out_features, in_features) — NOT pre-transposed
|
|
791
|
+
bias : (out_features,) | None
|
|
792
|
+
output: (..., out_features)
|
|
793
|
+
|
|
794
|
+
Routes through the existing mm / bmm / addmm primitives with an
|
|
795
|
+
implicit transpose on `weight`. No new kernel launches beyond what
|
|
796
|
+
these helpers already do.
|
|
797
|
+
"""
|
|
798
|
+
w_t = weight.t()
|
|
799
|
+
if bias is None:
|
|
800
|
+
return matmul_wrapper(input, w_t)
|
|
801
|
+
# matmul then broadcast-add — matches torch's handling for ND inputs.
|
|
802
|
+
# For pure 2D we could use addmm directly, but matmul_wrapper already
|
|
803
|
+
# takes the mm path and `add` broadcasts cleanly.
|
|
804
|
+
out = matmul_wrapper(input, w_t)
|
|
805
|
+
return _ew.add(out, bias)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def addmm(bias, a, b,
|
|
809
|
+
beta: float = 1.0, alpha: float = 1.0, _epilogue: int = 0) :
|
|
810
|
+
"""C = beta * bias + alpha * (A @ B).
|
|
811
|
+
Kernel accumulates in fp32 (hardware). Output matches input dtype.
|
|
812
|
+
DtypeEngine handles overflow protection externally.
|
|
813
|
+
|
|
814
|
+
Small-M fast path (M <= 4): per-row addmv. Bias is broadcast the same
|
|
815
|
+
way torch.addmm does: (N,), (1, N), or (M, N).
|
|
816
|
+
|
|
817
|
+
`_epilogue` (fusion_vertical pass): fused elementwise epilogue code
|
|
818
|
+
(0 none, 1 silu, 2 gelu exact, 3 gelu tanh) with per-stage rounding
|
|
819
|
+
emulation in-kernel; M <= 4 decode path stays unfused (see mm()).
|
|
820
|
+
"""
|
|
821
|
+
a = _core._ensure_cuda(a)
|
|
822
|
+
b = _core._ensure_cuda(b)
|
|
823
|
+
bias = _core._ensure_cuda(bias)
|
|
824
|
+
|
|
825
|
+
# Hardware-gated fp16→fp32 input upcast — same rationale as mm().
|
|
826
|
+
# Use nbx_dtype for guard comparisons; .dtype returns triton.language.dtype.
|
|
827
|
+
# The operand decisions are `launch_keys.addmm_dtypes` — the function the derived census
|
|
828
|
+
# keys with (NBX_FORCE_FP32_ACCUM first; PROMOTE_A / PROMOTE_B in-kernel widenings on hardware
|
|
829
|
+
# without native bf16; any other mismatch widened to the widest; the bias widened in-kernel
|
|
830
|
+
# under an fp32 activation, else cast to the activation's dtype). The wrapper only casts.
|
|
831
|
+
a_to, b_to, bias_to, promote_a, promote_b, promote_bias = _lk.addmm_dtypes(
|
|
832
|
+
a.nbx_dtype, b.nbx_dtype, bias.nbx_dtype, _core._NBX_HAS_NATIVE_BF16, NBX_FORCE_FP32_ACCUM)
|
|
833
|
+
if a.nbx_dtype != a_to:
|
|
834
|
+
a = a.to(a_to)
|
|
835
|
+
if b.nbx_dtype != b_to:
|
|
836
|
+
b = b.to(b_to)
|
|
837
|
+
if bias.nbx_dtype != bias_to:
|
|
838
|
+
bias = bias.to(bias_to)
|
|
839
|
+
|
|
840
|
+
if a.ndim > 2:
|
|
841
|
+
orig_lead = a.shape[:-1]
|
|
842
|
+
K_ = a.shape[-1]
|
|
843
|
+
batch = a.numel() // K_
|
|
844
|
+
a2d = a.contiguous().view(batch, K_)
|
|
845
|
+
out2d = addmm(bias, a2d, b, beta=beta, alpha=alpha, _epilogue=_epilogue)
|
|
846
|
+
return out2d.view(*orig_lead, out2d.shape[-1])
|
|
847
|
+
|
|
848
|
+
M, K = a.shape
|
|
849
|
+
K2, N = b.shape
|
|
850
|
+
# Named, not bare: a shape mismatch here reaches the census as
|
|
851
|
+
# "AssertionError: " with nothing in it, and a failure that carries no
|
|
852
|
+
# numbers costs a whole reproduction run to say what the raise could
|
|
853
|
+
# have said itself (2026-09-23, the two PixArt -MS containers).
|
|
854
|
+
if K != K2:
|
|
855
|
+
raise AssertionError(
|
|
856
|
+
f"addmm shape mismatch: ({M}, {K}) @ ({K2}, {N}) — the contracted "
|
|
857
|
+
f"dimension differs, {K} against {K2}")
|
|
858
|
+
|
|
859
|
+
if M <= 4:
|
|
860
|
+
a = a.contiguous()
|
|
861
|
+
bias = bias.contiguous()
|
|
862
|
+
bt = b.t()
|
|
863
|
+
c = NBXTensor.empty((M, N), device=a.device,
|
|
864
|
+
dtype=_matmul_out_dtype(a, M))
|
|
865
|
+
bias_per_row = (bias.ndim == 2 and bias.shape[0] == M)
|
|
866
|
+
if bias.ndim == 2 and bias.shape[0] == 1:
|
|
867
|
+
bias_shared = bias[0]
|
|
868
|
+
elif bias.ndim == 1:
|
|
869
|
+
bias_shared = bias
|
|
870
|
+
else:
|
|
871
|
+
bias_shared = None
|
|
872
|
+
for i in range(M):
|
|
873
|
+
br = bias[i] if bias_per_row else bias_shared
|
|
874
|
+
c[i] = addmv_wrapper(br, bt, a[i], beta=beta, alpha=alpha)
|
|
875
|
+
return _apply_matmul_epilogue(c, _epilogue)
|
|
876
|
+
|
|
877
|
+
if bias.ndim == 2 and bias.shape[0] != 1:
|
|
878
|
+
# The kernel adds ONE bias row [N] to every output row: a per-row bias above the decode rows would be computed
|
|
879
|
+
# with its first row only. Refused by name, never computed wrong (no catalogue graph forms one: its 10 478
|
|
880
|
+
# addmm carry a bias [N], read 2026-10-29).
|
|
881
|
+
raise ValueError(f"addmm: a per-row bias {tuple(bias.shape)} is served at M <= 4 only (here M = {M}); "
|
|
882
|
+
f"the kernel adds one bias row [N] to every row")
|
|
883
|
+
a = a.contiguous()
|
|
884
|
+
# A pre-transposed weight is walked by its strides (as mm); only a broadcast
|
|
885
|
+
# (stride 0) operand is materialised, the kernel's tile loads need a real address.
|
|
886
|
+
if any(st == 0 for st in b.stride()):
|
|
887
|
+
b = b.contiguous()
|
|
888
|
+
bias = bias.contiguous()
|
|
889
|
+
out_dtype = _matmul_out_dtype(a, M, force_fp32=promote_a)
|
|
890
|
+
c = NBXTensor.empty((M, N), device=a.device, dtype=out_dtype)
|
|
891
|
+
ieee = (not _core._NBX_HAS_NATIVE_BF16) and (out_dtype == NBXDtype.float32)
|
|
892
|
+
# Row banding above 2^31 output elements, exactly as `mm` — see the comment there. The
|
|
893
|
+
# bias is indexed by COLUMN and is shared by every band, so it is passed whole.
|
|
894
|
+
bands = [(0, M)]
|
|
895
|
+
if N > 0 and M * N > _NBX_MM_MAX_OUTPUT_ELEMS:
|
|
896
|
+
per = max(1, _NBX_MM_MAX_OUTPUT_ELEMS // N)
|
|
897
|
+
bands = [(r, min(r + per, M)) for r in range(0, M, per)]
|
|
898
|
+
m_bucket = _bucket_of("M", M)
|
|
899
|
+
_mu_tile = _gemm_unit_tile(a, b, M, N) if bias.ndim == 1 or (bias.ndim == 2 and bias.shape[0] == 1) else None
|
|
900
|
+
for r0, r1 in bands:
|
|
901
|
+
rows = r1 - r0
|
|
902
|
+
a_b = a[r0:r1] if len(bands) > 1 else a
|
|
903
|
+
c_b = c[r0:r1] if len(bands) > 1 else c
|
|
904
|
+
args = (a_b, b, bias, c_b, rows, N, K, m_bucket, a_b.stride(0), a_b.stride(1), b.stride(0), b.stride(1),
|
|
905
|
+
c_b.stride(0), c_b.stride(1), alpha, beta)
|
|
906
|
+
kwargs = dict(IEEE_PRECISION=ieee, PROMOTE_B=promote_b, PROMOTE_A=promote_a, PROMOTE_BIAS=promote_bias,
|
|
907
|
+
EPILOGUE=_epilogue)
|
|
908
|
+
tile = _gemm_route(addmm_kernel, args, kwargs, _mu_tile)
|
|
909
|
+
if tile is not None:
|
|
910
|
+
_gemm_m8n8k4(a_b, b, bias, c_b, alpha, beta, promote_bias, _epilogue, tile)
|
|
911
|
+
continue
|
|
912
|
+
grid = (lambda META, _m=rows: (triton.cdiv(_m, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),))
|
|
913
|
+
_core._autotune_headroom_guard(addmm_kernel[grid])(*args, **kwargs)
|
|
914
|
+
return c
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
# Epilogue string -> kernel code mapping (fusion_vertical pass). Kept in
|
|
918
|
+
# ONE place so the emitter, the wrappers, and the kernel constexpr codes
|
|
919
|
+
# cannot drift apart.
|
|
920
|
+
_MATMUL_EPILOGUE_CODES = {
|
|
921
|
+
("silu", "none"): 1,
|
|
922
|
+
("silu", "tanh"): 1, # approximate is silu-irrelevant; tolerate
|
|
923
|
+
("gelu", "none"): 2,
|
|
924
|
+
("gelu", "tanh"): 3,
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def mm_epilogue_wrapper(a, b, epilogue: str = "silu",
|
|
929
|
+
approximate: str = "none"):
|
|
930
|
+
"""custom::mm_epilogue — aten::mm fused with a unary elementwise
|
|
931
|
+
epilogue (fusion_vertical pass, EXACT gate). One kernel launch for
|
|
932
|
+
the pair; byte-identical to the unfused pair via per-stage rounding
|
|
933
|
+
emulation + shared autotune config (same kernel function)."""
|
|
934
|
+
return mm(a, b, _epilogue=_MATMUL_EPILOGUE_CODES[(epilogue, approximate)])
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def addmm_epilogue_wrapper(bias, a, b, beta: float = 1.0,
|
|
938
|
+
alpha: float = 1.0, epilogue: str = "silu",
|
|
939
|
+
approximate: str = "none"):
|
|
940
|
+
"""custom::addmm_epilogue — aten::addmm fused with a unary
|
|
941
|
+
elementwise epilogue (fusion_vertical pass, EXACT gate)."""
|
|
942
|
+
return addmm(bias, a, b, beta=beta, alpha=alpha,
|
|
943
|
+
_epilogue=_MATMUL_EPILOGUE_CODES[(epilogue, approximate)])
|
|
944
|
+
#: A matmul launch whose OUTPUT holds more than this many elements is split along M. The
|
|
945
|
+
#: boundary is 2^31 because that is where an int32 element offset wraps; the margin below it
|
|
946
|
+
#: is deliberate, so a tile that straddles the boundary is never the last one.
|
|
947
|
+
_NBX_MM_MAX_OUTPUT_ELEMS = int(os.environ.get("NBX_MM_MAX_OUTPUT_ELEMS", str(2 ** 31 - 2 ** 20)))
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
# ---------------------------------------------------------------------------
|
|
951
|
+
# Dot product (2 vectors → scalar)
|
|
952
|
+
# ---------------------------------------------------------------------------
|
|
953
|
+
|
|
954
|
+
def dot_wrapper(x, y) :
|
|
955
|
+
"""Dot product: sum(x * y). Two-phase reduction for large N."""
|
|
956
|
+
x, y = x.contiguous().view(-1), y.contiguous().view(-1)
|
|
957
|
+
N = x.numel()
|
|
958
|
+
BLOCK_SIZE = min(triton.next_power_of_2(N), 4096)
|
|
959
|
+
|
|
960
|
+
if N <= BLOCK_SIZE:
|
|
961
|
+
out = NBXTensor.empty([], dtype=NBXDtype.float32, device=x.device)
|
|
962
|
+
set_device(x)
|
|
963
|
+
dot_kernel_small[(1,)](x, y, out, N, BLOCK_SIZE=BLOCK_SIZE)
|
|
964
|
+
return out.to(x.dtype)
|
|
965
|
+
else:
|
|
966
|
+
mid_size = triton.cdiv(N, BLOCK_SIZE)
|
|
967
|
+
mid = NBXTensor.empty(mid_size, dtype=NBXDtype.float32, device=x.device)
|
|
968
|
+
set_device(x)
|
|
969
|
+
dot_kernel_partial[(mid_size,)](x, y, mid, N, BLOCK_SIZE=BLOCK_SIZE)
|
|
970
|
+
BLOCK_MID = triton.next_power_of_2(mid_size)
|
|
971
|
+
out = NBXTensor.empty([], dtype=NBXDtype.float32, device=x.device)
|
|
972
|
+
set_device(mid)
|
|
973
|
+
dot_kernel_reduce[(1,)](mid, out, mid_size, BLOCK_MID=BLOCK_MID)
|
|
974
|
+
return out.to(x.dtype)
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
# ---------------------------------------------------------------------------
|
|
978
|
+
# MV (matrix-vector multiply)
|
|
979
|
+
# ---------------------------------------------------------------------------
|
|
980
|
+
|
|
981
|
+
def mv_wrapper(mat, vec) :
|
|
982
|
+
"""Matrix-vector multiply: out[i] = sum_j(mat[i, j] * vec[j]).
|
|
983
|
+
|
|
984
|
+
The SIMT gemv_vec kernel (ops/gemv_vec.py, adopted 2026-08-24 over the FlagGems mv kernel: its (BLOCK_N,)
|
|
985
|
+
per-chunk-reduced accumulator, all of K inside one program in fixed order — no split-K atomics). Its tile —
|
|
986
|
+
rows per program, K chunk, warps, stages — is the certified configuration of the key (workstream 1, small-M
|
|
987
|
+
GEMM): the N and K buckets from the launcher's own `bucket_of` and the dtypes of its tensors, the key the derived
|
|
988
|
+
census forms (`launch_keys.mv_launches`).
|
|
989
|
+
"""
|
|
990
|
+
# A wrapper that copies whatever it is given cannot honour a caller that took care not to
|
|
991
|
+
# give it a copy. The gemv kernels take the matrix's two strides and the vector's one; what
|
|
992
|
+
# they need is K-contiguous ROWS (stride(1) == 1: the pre-transposed weight layout, where
|
|
993
|
+
# the loads vectorise) — a matrix whose rows are strided is copied once here and the line
|
|
994
|
+
# says why; a vector is read through its stride, a broadcast (stride 0) materialised.
|
|
995
|
+
if mat.stride(1) != 1:
|
|
996
|
+
mat = mat.contiguous() # justified: the kernel's rows must be K-contiguous
|
|
997
|
+
if vec.stride(0) == 0:
|
|
998
|
+
vec = vec.contiguous() # justified: a broadcast vector has no stride to walk
|
|
999
|
+
N, K = mat.shape
|
|
1000
|
+
out = NBXTensor.empty(N, device=mat.device, dtype=_matmul_out_dtype(mat))
|
|
1001
|
+
set_device(mat)
|
|
1002
|
+
from nbxtensor.ops.gemv_vec import gemv_vec_kernel
|
|
1003
|
+
gemv_vec_kernel[lambda META: (triton.cdiv(N, META['BLOCK_N']),)](
|
|
1004
|
+
mat, vec, out,
|
|
1005
|
+
N, K, _bucket_of("N", N), _bucket_of("K", K),
|
|
1006
|
+
mat.stride(0), mat.stride(1),
|
|
1007
|
+
vec.stride(0),
|
|
1008
|
+
out.stride(0),
|
|
1009
|
+
)
|
|
1010
|
+
return out
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
# ---------------------------------------------------------------------------
|
|
1014
|
+
# Baddbmm (batched addmm)
|
|
1015
|
+
# ---------------------------------------------------------------------------
|
|
1016
|
+
|
|
1017
|
+
def baddbmm_wrapper(
|
|
1018
|
+
input, batch1, batch2,
|
|
1019
|
+
beta: float = 1.0, alpha: float = 1.0,
|
|
1020
|
+
) :
|
|
1021
|
+
"""Batched addmm: out = beta * input + alpha * (batch1 @ batch2)."""
|
|
1022
|
+
batch1, batch2 = batch1.contiguous(), batch2.contiguous()
|
|
1023
|
+
input = input.contiguous()
|
|
1024
|
+
B, M, K = batch1.shape
|
|
1025
|
+
_, K2, N = batch2.shape
|
|
1026
|
+
if K != K2:
|
|
1027
|
+
raise AssertionError(
|
|
1028
|
+
f"baddbmm shape mismatch: ({B}, {M}, {K}) @ (..., {K2}, {N}) — the "
|
|
1029
|
+
f"contracted dimension differs, {K} against {K2}")
|
|
1030
|
+
|
|
1031
|
+
output = NBXTensor.empty((B, M, N), device=batch1.device, dtype=batch1.dtype)
|
|
1032
|
+
|
|
1033
|
+
# Handle bias broadcasting: input may be [M, N], [B, M, N], [1, M, N],
|
|
1034
|
+
# [B, 1, N], [M, 1], etc. Any size-1 dim broadcasts with stride 0 —
|
|
1035
|
+
# keeping the memory stride of a size-1 dim would walk PAST the bias
|
|
1036
|
+
# row/column (wrong values, OOB on the last batch).
|
|
1037
|
+
if input.ndim == 2:
|
|
1038
|
+
bias_batch_stride = 0
|
|
1039
|
+
bias_m_stride = input.stride(0) if input.shape[0] > 1 else 0
|
|
1040
|
+
bias_n_stride = input.stride(1) if input.shape[1] > 1 else 0
|
|
1041
|
+
else:
|
|
1042
|
+
bias_batch_stride = input.stride(0) if input.shape[0] > 1 else 0
|
|
1043
|
+
bias_m_stride = input.stride(-2) if input.shape[-2] > 1 else 0
|
|
1044
|
+
bias_n_stride = input.stride(-1) if input.shape[-1] > 1 else 0
|
|
1045
|
+
|
|
1046
|
+
_mu_tile = _gemm_unit_tile(batch1, batch2, M, N)
|
|
1047
|
+
if _mu_tile is not None: # the matrix unit (same epilogue: alpha * acc + beta * bias, cast to output)
|
|
1048
|
+
_gemm_m8n8k4(batch1, batch2, input, output, alpha, beta, False, 0, _mu_tile,
|
|
1049
|
+
bias_strides=(bias_batch_stride, bias_m_stride, bias_n_stride))
|
|
1050
|
+
return output
|
|
1051
|
+
|
|
1052
|
+
set_device(batch1)
|
|
1053
|
+
# IEEE_PRECISION=True preserves the kernel's historical
|
|
1054
|
+
# `allow_tf32=False` dot semantics for fp32 inputs (ignored by tl.dot
|
|
1055
|
+
# for half inputs). HAS_BIAS=True keeps the beta*bias epilogue.
|
|
1056
|
+
# gridDim.z caps at _GRID_Z_MAX — chunk the batch past it (the loop
|
|
1057
|
+
# body runs exactly once below the cap, no narrow() on the fast path).
|
|
1058
|
+
for z0 in range(0, B, _GRID_Z_MAX):
|
|
1059
|
+
zb = min(_GRID_Z_MAX, B - z0)
|
|
1060
|
+
b1_z = batch1 if zb == B else batch1.narrow(0, z0, zb)
|
|
1061
|
+
b2_z = batch2 if zb == B else batch2.narrow(0, z0, zb)
|
|
1062
|
+
out_z = output if zb == B else output.narrow(0, z0, zb)
|
|
1063
|
+
bias_z = (input if (zb == B or bias_batch_stride == 0)
|
|
1064
|
+
else input.narrow(0, z0, zb))
|
|
1065
|
+
grid = lambda META, _zb=zb: (
|
|
1066
|
+
triton.cdiv(M, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),
|
|
1067
|
+
1,
|
|
1068
|
+
_zb,
|
|
1069
|
+
)
|
|
1070
|
+
_core._autotune_headroom_guard(baddbmm_kernel[grid])(
|
|
1071
|
+
b1_z, b2_z, out_z, bias_z,
|
|
1072
|
+
alpha, beta,
|
|
1073
|
+
M, N, K, _bucket_of("M", M), _bucket_of("N", N), _bucket_of("K", K),
|
|
1074
|
+
batch1.stride(0), batch1.stride(1), batch1.stride(2),
|
|
1075
|
+
batch2.stride(0), batch2.stride(1), batch2.stride(2),
|
|
1076
|
+
output.stride(0), output.stride(1), output.stride(2),
|
|
1077
|
+
bias_batch_stride, bias_m_stride, bias_n_stride,
|
|
1078
|
+
IEEE_PRECISION=True,
|
|
1079
|
+
PROMOTE_B=False,
|
|
1080
|
+
HAS_BIAS=True,
|
|
1081
|
+
)
|
|
1082
|
+
return output
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
# ---------------------------------------------------------------------------
|
|
1086
|
+
# Addmv (matrix-vector with bias)
|
|
1087
|
+
# ---------------------------------------------------------------------------
|
|
1088
|
+
|
|
1089
|
+
def addmv_wrapper(
|
|
1090
|
+
input, mat, vec,
|
|
1091
|
+
beta: float = 1.0, alpha: float = 1.0,
|
|
1092
|
+
) :
|
|
1093
|
+
"""Matrix-vector with bias: out = beta * input + alpha * (mat @ vec)."""
|
|
1094
|
+
mat, vec = mat.contiguous(), vec.contiguous()
|
|
1095
|
+
input = input.contiguous()
|
|
1096
|
+
N, M = mat.shape
|
|
1097
|
+
out = NBXTensor.empty(N, device=mat.device, dtype=_matmul_out_dtype(mat))
|
|
1098
|
+
set_device(mat)
|
|
1099
|
+
# rows, reduction tile, warps: the certified configuration of the key (workstream 1, small-M GEMM) — the N and M
|
|
1100
|
+
# buckets and the dtypes of its tensors, the key the derived census forms (`launch_keys.addmv_launches`)
|
|
1101
|
+
addmv_kernel[lambda META: (triton.cdiv(N, META['BLOCK_N']),)](
|
|
1102
|
+
mat, vec, input, out,
|
|
1103
|
+
N, M, _bucket_of("N", N), _bucket_of("M", M),
|
|
1104
|
+
alpha, beta,
|
|
1105
|
+
mat.stride(0), mat.stride(1),
|
|
1106
|
+
vec.stride(0),
|
|
1107
|
+
input.stride(0),
|
|
1108
|
+
out.stride(0),
|
|
1109
|
+
)
|
|
1110
|
+
return out
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
def upsample_bilinear2d_wrapper(x, output_size, align_corners=False,
|
|
1114
|
+
scales_h=None, scales_w=None) :
|
|
1115
|
+
"""Bilinear upsampling."""
|
|
1116
|
+
N, C, IH, IW = x.shape
|
|
1117
|
+
if isinstance(output_size, (list, tuple)):
|
|
1118
|
+
OH, OW = output_size
|
|
1119
|
+
else:
|
|
1120
|
+
OH, OW = output_size, output_size
|
|
1121
|
+
output = NBXTensor.empty((N, C, OH, OW), device=x.device, dtype=x.dtype)
|
|
1122
|
+
if align_corners and OH > 1:
|
|
1123
|
+
scale_h = (IH - 1) / (OH - 1)
|
|
1124
|
+
scale_w = (IW - 1) / (OW - 1)
|
|
1125
|
+
offset = 0.0
|
|
1126
|
+
else:
|
|
1127
|
+
scale_h = IH / OH
|
|
1128
|
+
scale_w = IW / OW
|
|
1129
|
+
offset = 0.5
|
|
1130
|
+
# Grid must match the kernel's axis use: program_id(0)→ow (OW, BLOCK_X),
|
|
1131
|
+
# program_id(1)→oh (OH, BLOCK_Y). The OH/OW axes were swapped here, which is
|
|
1132
|
+
# invisible for square outputs (OH==OW, the image-upscaler case) but drops
|
|
1133
|
+
# most output columns when OH != OW — e.g. the 1-D-as-2-D path used by
|
|
1134
|
+
# upsample_linear1d (H=1), which the Kokoro iSTFT SineGen relies on.
|
|
1135
|
+
grid = (
|
|
1136
|
+
triton.cdiv(OW, _core._BILINEAR_BX),
|
|
1137
|
+
triton.cdiv(OH, _core._BILINEAR_BY),
|
|
1138
|
+
N * C,
|
|
1139
|
+
)
|
|
1140
|
+
set_device(x.contiguous())
|
|
1141
|
+
upsample_bilinear2d_kernel[grid](
|
|
1142
|
+
x.contiguous(), output,
|
|
1143
|
+
N, C, IH, IW, OH, OW,
|
|
1144
|
+
scale_h, scale_w, offset,
|
|
1145
|
+
BLOCK_X=_core._BILINEAR_BX, BLOCK_Y=_core._BILINEAR_BY,
|
|
1146
|
+
num_warps=4)
|
|
1147
|
+
return output
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
# ===========================================================================
|
|
1151
|
+
# INTERPOLATION — upsample_linear1d via bilinear2d
|
|
1152
|
+
# ===========================================================================
|
|
1153
|
+
|
|
1154
|
+
def upsample_linear1d_wrapper(x, output_size, align_corners: bool = False,
|
|
1155
|
+
scales=None) :
|
|
1156
|
+
"""upsample_linear1d via bilinear2d: [N,C,L] → unsqueeze → [N,C,1,L] → bilinear2d → squeeze.
|
|
1157
|
+
|
|
1158
|
+
Like upsample_nearest2d_wrapper: the traced graph stores BOTH the concrete
|
|
1159
|
+
trace output_size AND the scale factor. When the runtime input length differs
|
|
1160
|
+
from trace (variable-length audio decoders — Kokoro iSTFT source generator
|
|
1161
|
+
runs at 124 vs the 256 trace), the baked output_size is stale; PyTorch
|
|
1162
|
+
recomputes from the live input when a scale is present, so we do the same:
|
|
1163
|
+
out_l = round(runtime_input_len * scale). Bit-identical when trace == runtime
|
|
1164
|
+
(input_len * scale == baked output_size). Without this, a linear upsample
|
|
1165
|
+
would desync from its sibling nearest upsample (which already scale-recomputes).
|
|
1166
|
+
"""
|
|
1167
|
+
x = _core._ensure_cuda(x).contiguous()
|
|
1168
|
+
x_4d = x.unsqueeze(2) # [N, C, 1, L]
|
|
1169
|
+
|
|
1170
|
+
scale_val = None
|
|
1171
|
+
if scales is not None:
|
|
1172
|
+
scale_val = scales[0] if isinstance(scales, (list, tuple)) else float(scales)
|
|
1173
|
+
|
|
1174
|
+
if scale_val is not None:
|
|
1175
|
+
out_l = int(round(x.shape[-1] * scale_val))
|
|
1176
|
+
elif output_size is not None:
|
|
1177
|
+
out_l = output_size[0] if isinstance(output_size, (list, tuple)) else int(output_size)
|
|
1178
|
+
else:
|
|
1179
|
+
out_l = None
|
|
1180
|
+
output_size_2d = [1, out_l] if out_l is not None else None
|
|
1181
|
+
|
|
1182
|
+
# Pass output_size only; bilinear2d derives its interpolation scale from the
|
|
1183
|
+
# (input, output) shapes + align_corners.
|
|
1184
|
+
result_4d = upsample_bilinear2d_wrapper(x_4d, output_size_2d, align_corners)
|
|
1185
|
+
return result_4d.squeeze(2)
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
def mm_cost(a, b, epilogue: int = 0, native_bf16=None, force_accum=None, contract_half=None):
|
|
1189
|
+
"""`mm` on `a` [M, K] and `b` [K, N] (`_cost.Spec`s), in the wrapper's order: the operands cast to
|
|
1190
|
+
`launch_keys.mm_dtypes` (a dense copy each); then, at M <= 4, `a` made contiguous, the output [M, N] and, per row,
|
|
1191
|
+
the GEMV's own output (N at `matmul_out_dtype` for one row) — and a full copy of the weight when its rows are not
|
|
1192
|
+
K-contiguous (`mv_wrapper`: `b.t()` must have stride 1 along K; a pre-transposed weight view has it, a row-major
|
|
1193
|
+
one does not) — each freed once copied into the output's row, then an epilogue (SiLU, GELU) makes its output while
|
|
1194
|
+
the GEMM's is alive; above, `a` made contiguous, `b` only when broadcast (a zero stride), the output at
|
|
1195
|
+
`matmul_out_dtype` (fp32 when `a` was promoted), the matrix unit or the kernel allocating nothing more (row bands
|
|
1196
|
+
are views, the epilogue runs in the kernel). A local rebound frees its old buffer after the new one exists; the
|
|
1197
|
+
rest die at the return."""
|
|
1198
|
+
from nbxtensor.ops import _cost as C
|
|
1199
|
+
from nbxtensor.shape import is_contiguous
|
|
1200
|
+
native_bf16 = bool(_core._NBX_HAS_NATIVE_BF16) if native_bf16 is None else native_bf16
|
|
1201
|
+
force_accum = bool(NBX_FORCE_FP32_ACCUM) if force_accum is None else force_accum
|
|
1202
|
+
contract_half = bool(_core._NBX_ACTIVATIONS_FP16_SAFE) if contract_half is None else contract_half
|
|
1203
|
+
st = C._Steps()
|
|
1204
|
+
M, K = (int(d) for d in a.shape)
|
|
1205
|
+
N = int(b.shape[1])
|
|
1206
|
+
flops = 2 * M * N * K
|
|
1207
|
+
held = {}
|
|
1208
|
+
|
|
1209
|
+
def rebind(name, spec, label):
|
|
1210
|
+
i = st.alloc(label, spec.nbytes)
|
|
1211
|
+
old = held.get(name)
|
|
1212
|
+
held[name] = i
|
|
1213
|
+
if old is not None:
|
|
1214
|
+
st.free(old)
|
|
1215
|
+
return spec
|
|
1216
|
+
|
|
1217
|
+
a_to, b_to, promote_a, _promote_b = _lk.mm_dtypes(NBXDtype[a.dtype], NBXDtype[b.dtype], native_bf16, force_accum)
|
|
1218
|
+
if NBXDtype[a.dtype] != a_to:
|
|
1219
|
+
a = rebind("a", C.Spec.contiguous(a.shape, a_to.name), "a cast")
|
|
1220
|
+
if NBXDtype[b.dtype] != b_to:
|
|
1221
|
+
b = rebind("b", C.Spec.contiguous(b.shape, b_to.name), "b cast")
|
|
1222
|
+
if not is_contiguous(a.shape, a.strides):
|
|
1223
|
+
a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous")
|
|
1224
|
+
if M <= 4:
|
|
1225
|
+
out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, False, native_bf16, force_accum, contract_half)
|
|
1226
|
+
c = st.alloc("output", M * N * dtype_size(out), returned=epilogue == 0)
|
|
1227
|
+
row_out = _lk.matmul_out_dtype(NBXDtype[b.dtype], 1, False, native_bf16, force_accum, contract_half)
|
|
1228
|
+
k_contiguous_rows = int(b.strides[0]) == 1 # b.t() has stride 1 along K
|
|
1229
|
+
for _ in range(M):
|
|
1230
|
+
copy = None if k_contiguous_rows else st.alloc("the weight made K-contiguous (mv)", b.nbytes)
|
|
1231
|
+
r = st.alloc("a row's GEMV output", N * dtype_size(row_out))
|
|
1232
|
+
if copy is not None:
|
|
1233
|
+
st.free(copy)
|
|
1234
|
+
st.free(r)
|
|
1235
|
+
else:
|
|
1236
|
+
if any(int(x) == 0 for x in b.strides):
|
|
1237
|
+
b = rebind("b", C.Spec.contiguous(b.shape, b.dtype), "b broadcast made dense")
|
|
1238
|
+
out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, bool(promote_a), native_bf16, force_accum, contract_half)
|
|
1239
|
+
st.alloc("output", M * N * dtype_size(out), returned=True)
|
|
1240
|
+
return st.done(flops) # an epilogue runs inside the kernel here (EPILOGUE)
|
|
1241
|
+
if epilogue: # the GEMV rows: the epilogue after, its own output
|
|
1242
|
+
for i in st.nest(C.elementwise_unary(C.Spec.contiguous((M, N), out.name)), "epilogue"):
|
|
1243
|
+
st.buffers[i][4] = True # the epilogue's output is the one returned
|
|
1244
|
+
return st.done(flops)
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
def addmm_cost(bias, a, b, epilogue: int = 0, native_bf16=None, force_accum=None, contract_half=None):
|
|
1248
|
+
"""`addmm` on `bias` ([N], [1, N] or [M, N]), `a` [..., K] and `b` [K, N] (`_cost.Spec`s), in the wrapper's order:
|
|
1249
|
+
the three operands cast to `launch_keys.addmm_dtypes` (a dense copy each); an `a` of more than two dims made
|
|
1250
|
+
contiguous and flattened, the product computed on the flat rows (its view returned); then, at M <= 4, `a` and the
|
|
1251
|
+
bias made contiguous, the output [M, N] at `matmul_out_dtype` and per row the GEMV's own output (N for one row) —
|
|
1252
|
+
and the whole weight copied when `b.t()` is not contiguous (`addmv_wrapper` makes its matrix contiguous: a row-major
|
|
1253
|
+
weight; a pre-transposed weight view is) — each freed once copied into the output's row, then an epilogue's output
|
|
1254
|
+
beside the GEMM's; above, `a` and the bias made contiguous, `b` only when broadcast, the output at
|
|
1255
|
+
`matmul_out_dtype` (fp32 when `a` was promoted), the epilogue in the kernel. A bias of another shape is refused by
|
|
1256
|
+
name (the wrapper's own rows: shared [N] / [1, N], per-row [M, N] at M <= 4 only, as the wrapper)."""
|
|
1257
|
+
from nbxtensor.ops import _cost as C
|
|
1258
|
+
from nbxtensor.shape import is_contiguous
|
|
1259
|
+
native_bf16 = bool(_core._NBX_HAS_NATIVE_BF16) if native_bf16 is None else native_bf16
|
|
1260
|
+
force_accum = bool(NBX_FORCE_FP32_ACCUM) if force_accum is None else force_accum
|
|
1261
|
+
contract_half = bool(_core._NBX_ACTIVATIONS_FP16_SAFE) if contract_half is None else contract_half
|
|
1262
|
+
st = C._Steps()
|
|
1263
|
+
held = {}
|
|
1264
|
+
|
|
1265
|
+
def rebind(name, spec, label):
|
|
1266
|
+
i = st.alloc(label, spec.nbytes)
|
|
1267
|
+
old = held.get(name)
|
|
1268
|
+
held[name] = i
|
|
1269
|
+
if old is not None:
|
|
1270
|
+
st.free(old)
|
|
1271
|
+
return spec
|
|
1272
|
+
|
|
1273
|
+
a_to, b_to, bias_to, promote_a, _pb, _pbias = _lk.addmm_dtypes(
|
|
1274
|
+
NBXDtype[a.dtype], NBXDtype[b.dtype], NBXDtype[bias.dtype], native_bf16, force_accum)
|
|
1275
|
+
if NBXDtype[a.dtype] != a_to:
|
|
1276
|
+
a = rebind("a", C.Spec.contiguous(a.shape, a_to.name), "a cast")
|
|
1277
|
+
if NBXDtype[b.dtype] != b_to:
|
|
1278
|
+
b = rebind("b", C.Spec.contiguous(b.shape, b_to.name), "b cast")
|
|
1279
|
+
if NBXDtype[bias.dtype] != bias_to:
|
|
1280
|
+
bias = rebind("bias", C.Spec.contiguous(bias.shape, bias_to.name), "bias cast")
|
|
1281
|
+
if len(a.shape) > 2:
|
|
1282
|
+
if not is_contiguous(a.shape, a.strides):
|
|
1283
|
+
a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous (flattened)")
|
|
1284
|
+
rows = 1
|
|
1285
|
+
for d in a.shape[:-1]:
|
|
1286
|
+
rows *= int(d)
|
|
1287
|
+
a = C.Spec.contiguous((rows, int(a.shape[-1])), a.dtype)
|
|
1288
|
+
M, K = (int(d) for d in a.shape)
|
|
1289
|
+
N = int(b.shape[1])
|
|
1290
|
+
if not (len(bias.shape) == 1 or (len(bias.shape) == 2 and int(bias.shape[0]) in (1, M))):
|
|
1291
|
+
raise ValueError(f"addmm_cost: a bias of shape {tuple(bias.shape)} is none of the wrapper's ([N], [1, N], [M, N])")
|
|
1292
|
+
flops = 2 * M * N * K
|
|
1293
|
+
if not is_contiguous(a.shape, a.strides):
|
|
1294
|
+
a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous")
|
|
1295
|
+
if not is_contiguous(bias.shape, bias.strides):
|
|
1296
|
+
bias = rebind("bias", C.Spec.contiguous(bias.shape, bias.dtype), "bias made contiguous")
|
|
1297
|
+
if M <= 4:
|
|
1298
|
+
out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, False, native_bf16, force_accum, contract_half)
|
|
1299
|
+
st.alloc("output", M * N * dtype_size(out), returned=epilogue == 0)
|
|
1300
|
+
row_out = _lk.matmul_out_dtype(NBXDtype[b.dtype], 1, False, native_bf16, force_accum, contract_half)
|
|
1301
|
+
bt_contiguous = tuple(int(x) for x in b.strides) == (1, K) # b.t() is (N, K) row-major
|
|
1302
|
+
for _ in range(M):
|
|
1303
|
+
copy = None if bt_contiguous else st.alloc("the weight made contiguous (addmv)", b.nbytes)
|
|
1304
|
+
r = st.alloc("a row's GEMV output", N * dtype_size(row_out))
|
|
1305
|
+
if copy is not None:
|
|
1306
|
+
st.free(copy)
|
|
1307
|
+
st.free(r)
|
|
1308
|
+
if epilogue:
|
|
1309
|
+
for i in st.nest(C.elementwise_unary(C.Spec.contiguous((M, N), out.name)), "epilogue"):
|
|
1310
|
+
st.buffers[i][4] = True
|
|
1311
|
+
return st.done(flops)
|
|
1312
|
+
if len(bias.shape) == 2 and int(bias.shape[0]) != 1:
|
|
1313
|
+
raise ValueError(f"addmm_cost: a per-row bias {tuple(bias.shape)} is served at M <= 4 only (here M = {M}), "
|
|
1314
|
+
f"as the wrapper refuses it")
|
|
1315
|
+
if any(int(x) == 0 for x in b.strides):
|
|
1316
|
+
b = rebind("b", C.Spec.contiguous(b.shape, b.dtype), "b broadcast made dense")
|
|
1317
|
+
out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, bool(promote_a), native_bf16, force_accum, contract_half)
|
|
1318
|
+
st.alloc("output", M * N * dtype_size(out), returned=True)
|
|
1319
|
+
return st.done(flops)
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
def bmm_cost(a, b, allow_strided_b: bool = False, native_bf16=None, force_accum=None, contract_half=None,
|
|
1323
|
+
unit=None):
|
|
1324
|
+
"""`bmm` on `a` [B, M, K] and `b` [B, K, N] (`_cost.Spec`s): on the matrix unit (`launch_keys.matrix_unit_operands`)
|
|
1325
|
+
`a` made contiguous and `b` unless the caller lets it be walked by its strides, then the output; otherwise the
|
|
1326
|
+
operands at `launch_keys.bmm_dtypes` (a cast each, dense), made contiguous as above, then the output at
|
|
1327
|
+
`launch_keys.matmul_out_dtype` (fp32 for half inputs on hardware without native bf16 unless the component's precision
|
|
1328
|
+
contract holds). The facts the wrapper reads from the run (`native_bf16`, `force_accum`, `contract_half`) are the
|
|
1329
|
+
run's in force when None; `unit` the profile's matrix unit when None. A local rebound frees its old buffer after
|
|
1330
|
+
the new one exists; the rest die at the return."""
|
|
1331
|
+
from nbxtensor.ops import _cost as C
|
|
1332
|
+
from nbxtensor.shape import is_contiguous
|
|
1333
|
+
native_bf16 = bool(_core._NBX_HAS_NATIVE_BF16) if native_bf16 is None else native_bf16
|
|
1334
|
+
force_accum = bool(NBX_FORCE_FP32_ACCUM) if force_accum is None else force_accum
|
|
1335
|
+
contract_half = bool(_core._NBX_ACTIVATIONS_FP16_SAFE) if contract_half is None else contract_half
|
|
1336
|
+
st = C._Steps()
|
|
1337
|
+
Bz, M, K = (int(d) for d in a.shape)
|
|
1338
|
+
N = int(b.shape[2])
|
|
1339
|
+
flops = 2 * Bz * M * N * K
|
|
1340
|
+
ad, bd = NBXDtype[a.dtype], NBXDtype[b.dtype]
|
|
1341
|
+
held = {}
|
|
1342
|
+
|
|
1343
|
+
def rebind(name, spec, label):
|
|
1344
|
+
i = st.alloc(label, spec.nbytes)
|
|
1345
|
+
old = held.get(name)
|
|
1346
|
+
held[name] = i
|
|
1347
|
+
if old is not None:
|
|
1348
|
+
st.free(old)
|
|
1349
|
+
return spec
|
|
1350
|
+
|
|
1351
|
+
if _lk.matrix_unit_operands(ad, bd, M, unit) is not None:
|
|
1352
|
+
if not is_contiguous(a.shape, a.strides):
|
|
1353
|
+
a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous")
|
|
1354
|
+
if not allow_strided_b and not is_contiguous(b.shape, b.strides):
|
|
1355
|
+
b = rebind("b", C.Spec.contiguous(b.shape, b.dtype), "b made contiguous")
|
|
1356
|
+
out = _lk.matmul_out_dtype(ad, M, True, native_bf16, force_accum, contract_half)
|
|
1357
|
+
st.alloc("output", Bz * M * N * dtype_size(out), returned=True)
|
|
1358
|
+
return st.done(flops)
|
|
1359
|
+
a_to, b_to, _promote = _lk.bmm_dtypes(ad, bd, native_bf16, force_accum)
|
|
1360
|
+
if ad != a_to:
|
|
1361
|
+
a = rebind("a", C.Spec.contiguous(a.shape, a_to.name), "a cast")
|
|
1362
|
+
if bd != b_to:
|
|
1363
|
+
b = rebind("b", C.Spec.contiguous(b.shape, b_to.name), "b cast")
|
|
1364
|
+
if not is_contiguous(a.shape, a.strides):
|
|
1365
|
+
a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous")
|
|
1366
|
+
if not allow_strided_b and not is_contiguous(b.shape, b.strides):
|
|
1367
|
+
b = rebind("b", C.Spec.contiguous(b.shape, b.dtype), "b made contiguous")
|
|
1368
|
+
out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, True, native_bf16, force_accum, contract_half)
|
|
1369
|
+
st.alloc("output", Bz * M * N * dtype_size(out), returned=True)
|
|
1370
|
+
return st.done(flops)
|