Sync the source snapshot with iamwavecut/OrbitQuant@2d586f189ab8
Browse filesTracked kernel package sources mirrored 1:1 from native-kernels/orbitquant-packed-matmul at 2d586f189ab8c44ca10685a64e71aaab10c61430. Adds the CPU backend (AVX2/AVX-512/NEON + stable torch ABI), the CUDA W4A4 int8 runtime ops with the cold-L2 pipelined mma64 path, and the Metal skinny-batch GEMV; no generated build files.
- CARD.md +103 -8
- benchmarks/benchmark.py +149 -39
- build.toml +34 -1
- orbitquant_packed_matmul_cpu/cpu_isa.cpp +42 -0
- orbitquant_packed_matmul_cpu/cpu_kernel_args.h +75 -0
- orbitquant_packed_matmul_cpu/cpu_msvc_avx2.cpp +431 -0
- orbitquant_packed_matmul_cpu/cpu_pool.cpp +126 -0
- orbitquant_packed_matmul_cpu/cpu_pool.h +18 -0
- orbitquant_packed_matmul_cpu/cpu_threads.cpp +103 -0
- orbitquant_packed_matmul_cpu/cpu_threads.h +7 -0
- orbitquant_packed_matmul_cpu/packed_adaln_cpu.cpp +878 -0
- orbitquant_packed_matmul_cpu/packed_matmul_cpu.cpp +201 -0
- orbitquant_packed_matmul_cpu/packed_matmul_cpu.h +59 -0
- orbitquant_packed_matmul_cpu/packed_matmul_neon.cpp +484 -0
- orbitquant_packed_matmul_cpu/packed_matmul_scalar.cpp +95 -0
- orbitquant_packed_matmul_cpu/packed_matmul_x86.cpp +616 -0
- orbitquant_packed_matmul_cpu/packed_matmul_x86_avx512.cpp +851 -0
- orbitquant_packed_matmul_cpu/quantize_activations_cpu.cpp +867 -0
- orbitquant_packed_matmul_cuda/packed_matmul.cu +1671 -24
- orbitquant_packed_matmul_metal/packed_matmul.metal +425 -72
- orbitquant_packed_matmul_metal/packed_matmul.mm +90 -12
- scripts/prepare_wheel_project.py +158 -0
- tests/test_packed_matmul.py +726 -26
- torch-ext/orbitquant_packed_matmul/__init__.py +337 -5
- torch-ext/torch_binding.cpp +53 -2
- torch-ext/torch_binding.h +80 -6
CARD.md
CHANGED
|
@@ -28,29 +28,90 @@ Inputs:
|
|
| 28 |
|
| 29 |
- `x`: contiguous or reshapeable tensor with shape `[..., in_features]`.
|
| 30 |
- `packed_weight_indices`: `uint8` low-bit packed row-major codebook indices.
|
| 31 |
-
- `row_norms`:
|
|
|
|
| 32 |
- `centroids`: `float32` Lloyd-Max centroids with shape `[2**bits]`.
|
| 33 |
- `bias`: optional projection bias.
|
| 34 |
|
| 35 |
`x` may be `float32`, `float16`, or `bfloat16`. The output has shape
|
| 36 |
`[..., out_features]` and the same dtype as `x`.
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
## Build And Test
|
| 39 |
|
| 40 |
```bash
|
| 41 |
-
nix --option sandbox relaxed
|
| 42 |
-
|
|
|
|
|
|
|
| 43 |
```
|
| 44 |
|
| 45 |
The build produces ABI3 Hugging Face Kernels artifacts under `build/` for the
|
| 46 |
supported backend variants on the current platform. On macOS, `sandbox relaxed`
|
| 47 |
-
or enabled Nix sandboxing is required by `kernel-builder`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
For direct local imports, add the matching `build/torch*-<backend>-<platform>`
|
| 50 |
directory to `PYTHONPATH`; the `torch*` variant must match the runtime PyTorch
|
| 51 |
-
version
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
```bash
|
| 56 |
export LOCAL_KERNELS="WaveCut/orbitquant-packed-matmul=/path/to/build/torch212-metal-aarch64-darwin"
|
|
@@ -77,9 +138,15 @@ python benchmarks/benchmark.py \
|
|
| 77 |
--iters 20
|
| 78 |
```
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
The script prints JSON with `packed_seconds_per_iter`,
|
| 81 |
`predequantized_f_linear_seconds_per_iter`,
|
| 82 |
-
`dequantize_then_f_linear_seconds_per_iter`,
|
|
|
|
|
|
|
| 83 |
`packed_vs_predequantized_f_linear_speedup`,
|
| 84 |
`packed_vs_dequantize_then_f_linear_speedup`, compatibility aliases
|
| 85 |
`reference_seconds_per_iter` and `packed_vs_reference_speedup`, and
|
|
@@ -91,3 +158,31 @@ It also reports storage accounting for the packed weight path:
|
|
| 91 |
`packed_weight_path_vs_materialized_weight_ratio`. These values describe only
|
| 92 |
the weight-side storage used by this operator; they are not end-to-end model
|
| 93 |
VRAM measurements.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
- `x`: contiguous or reshapeable tensor with shape `[..., in_features]`.
|
| 30 |
- `packed_weight_indices`: `uint8` low-bit packed row-major codebook indices.
|
| 31 |
+
- `row_norms`: row norms with shape `[out_features]`; CUDA consumes the
|
| 32 |
+
artifact's `bfloat16` values directly, while Metal uses `float32` internally.
|
| 33 |
- `centroids`: `float32` Lloyd-Max centroids with shape `[2**bits]`.
|
| 34 |
- `bias`: optional projection bias.
|
| 35 |
|
| 36 |
`x` may be `float32`, `float16`, or `bfloat16`. The output has shape
|
| 37 |
`[..., out_features]` and the same dtype as `x`.
|
| 38 |
|
| 39 |
+
The CUDA package also exports the operations used by OrbitQuant's W4A4 runtime:
|
| 40 |
+
|
| 41 |
+
- `quantize_activations_int8`: token norm, RPBH/FWHT, nearest-codebook
|
| 42 |
+
assignment, and INT8-surrogate output in one native launch.
|
| 43 |
+
- `quantize_activations_packed_w4`: the same activation path with packed 4-bit
|
| 44 |
+
output for the direct packed matmul fallback.
|
| 45 |
+
- `matmul_packed_w4a4_int8`: direct packed A4/W4 CUDA MMA with fused token norm,
|
| 46 |
+
row norm, surrogate scales, and bias epilogue.
|
| 47 |
+
|
| 48 |
+
On CUDA compute capability 8.0 or newer, OrbitQuant normally combines
|
| 49 |
+
`quantize_activations_int8` with chunked packed-weight decode and Torch's
|
| 50 |
+
CUTLASS-backed INT8 matmul. The direct packed MMA operation remains available
|
| 51 |
+
when that path is unsupported. Neither path materializes a complete BF16/FP16
|
| 52 |
+
weight matrix.
|
| 53 |
+
|
| 54 |
## Build And Test
|
| 55 |
|
| 56 |
```bash
|
| 57 |
+
nix --option sandbox relaxed --option max-jobs 1 --option cores 8 \
|
| 58 |
+
run .#build-and-copy -L
|
| 59 |
+
nix --option sandbox relaxed --option max-jobs 1 --option cores 8 \
|
| 60 |
+
run .#ci-test -L
|
| 61 |
```
|
| 62 |
|
| 63 |
The build produces ABI3 Hugging Face Kernels artifacts under `build/` for the
|
| 64 |
supported backend variants on the current platform. On macOS, `sandbox relaxed`
|
| 65 |
+
or enabled Nix sandboxing is required by `kernel-builder`. The commands build
|
| 66 |
+
local files only; they do not upload to Kernel Hub.
|
| 67 |
+
|
| 68 |
+
For a faster CUDA-only development build on a machine with a matching Torch and
|
| 69 |
+
CUDA toolchain:
|
| 70 |
+
|
| 71 |
+
```bash
|
| 72 |
+
cargo install --git https://github.com/huggingface/kernels hf-kernel-builder
|
| 73 |
+
kernel-builder check-config .
|
| 74 |
+
kernel-builder create-pyproject -f .
|
| 75 |
+
TORCH_CUDA_ARCH_LIST="8.9" CUDACXX=/usr/local/cuda/bin/nvcc \
|
| 76 |
+
python setup.py build_kernel
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
For a local Metal build compatible with macOS 15 and newer:
|
| 80 |
+
|
| 81 |
+
```bash
|
| 82 |
+
cargo install --git https://github.com/huggingface/kernels hf-kernel-builder
|
| 83 |
+
kernel-builder check-config .
|
| 84 |
+
kernel-builder create-pyproject -f .
|
| 85 |
+
MACOSX_DEPLOYMENT_TARGET=15.0 \
|
| 86 |
+
CMAKE_ARGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=15.0" \
|
| 87 |
+
python setup.py build_kernel
|
| 88 |
+
kernel-builder check-abi --macos 15.0 --python-abi 3.9 .
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
This generated project is for local testing and must not be committed or
|
| 92 |
+
distributed without a successful `kernel-builder check-abi`. Use the Nix build
|
| 93 |
+
for redistributable variants.
|
| 94 |
|
| 95 |
For direct local imports, add the matching `build/torch*-<backend>-<platform>`
|
| 96 |
directory to `PYTHONPATH`; the `torch*` variant must match the runtime PyTorch
|
| 97 |
+
version:
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
export PYTHONPATH="/path/to/build/torch212-metal-aarch64-darwin:$PYTHONPATH"
|
| 101 |
+
python -c "import orbitquant_packed_matmul; print(orbitquant_packed_matmul)"
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
For PyTorch 2.9 CUDA inference, set the allocator before starting Python when
|
| 105 |
+
minimum reserved memory is important:
|
| 106 |
+
|
| 107 |
+
```bash
|
| 108 |
+
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True python generate.py
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
OrbitQuant detects that importable package before trying any Hub loader. For
|
| 112 |
+
Hugging Face `kernels` local loading instead, set `LOCAL_KERNELS` to the same
|
| 113 |
+
built variant directory containing `metadata.json`, not to the source package
|
| 114 |
+
root:
|
| 115 |
|
| 116 |
```bash
|
| 117 |
export LOCAL_KERNELS="WaveCut/orbitquant-packed-matmul=/path/to/build/torch212-metal-aarch64-darwin"
|
|
|
|
| 138 |
--iters 20
|
| 139 |
```
|
| 140 |
|
| 141 |
+
`--rows` accepts a comma-separated sweep (the default covers decode-bound
|
| 142 |
+
small batches and GEMM-bound large batches), and `--dtype` selects the
|
| 143 |
+
activation dtype explicitly. Headline timings are hot-loop medians.
|
| 144 |
+
|
| 145 |
The script prints JSON with `packed_seconds_per_iter`,
|
| 146 |
`predequantized_f_linear_seconds_per_iter`,
|
| 147 |
+
`dequantize_then_f_linear_seconds_per_iter` (all hot-loop medians), the
|
| 148 |
+
per-path `*_hot_mean_seconds`, `*_hot_median_seconds`, and
|
| 149 |
+
`*_hot_p95_seconds` distributions,
|
| 150 |
`packed_vs_predequantized_f_linear_speedup`,
|
| 151 |
`packed_vs_dequantize_then_f_linear_speedup`, compatibility aliases
|
| 152 |
`reference_seconds_per_iter` and `packed_vs_reference_speedup`, and
|
|
|
|
| 158 |
`packed_weight_path_vs_materialized_weight_ratio`. These values describe only
|
| 159 |
the weight-side storage used by this operator; they are not end-to-end model
|
| 160 |
VRAM measurements.
|
| 161 |
+
|
| 162 |
+
### Metal reference results
|
| 163 |
+
|
| 164 |
+
Measured on an Apple M2 Max with Torch 2.12.1, FP16 activations, W4 packed
|
| 165 |
+
weights, transformer-scale shapes (hot-loop medians over 30 iterations; each
|
| 166 |
+
iteration synchronizes, so sub-millisecond rows include the MPS submit
|
| 167 |
+
floor):
|
| 168 |
+
|
| 169 |
+
| Shape (rows x in x out) | Packed Metal | Resident FP16 `F.linear` | Materialize + `F.linear` | Packed vs resident | Packed vs materialize |
|
| 170 |
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
| 171 |
+
| 1 x 3072 x 3072 | 0.330 ms | 0.200 ms | 1.791 ms | 0.60x | 5.42x |
|
| 172 |
+
| 4 x 3072 x 3072 | 0.377 ms | 0.215 ms | 1.768 ms | 0.57x | 4.69x |
|
| 173 |
+
| 32 x 3072 x 3072 | 0.367 ms | 0.312 ms | 1.700 ms | 0.85x | 4.63x |
|
| 174 |
+
| 512 x 3072 x 3072 | 1.355 ms | 1.077 ms | 2.331 ms | 0.79x | 1.72x |
|
| 175 |
+
| 512 x 3072 x 12288 | 5.138 ms | 3.907 ms | 9.312 ms | 0.76x | 1.81x |
|
| 176 |
+
| 1 x 3072 x 12288 | 0.417 ms | 0.383 ms | 6.202 ms | 0.92x | 14.88x |
|
| 177 |
+
|
| 178 |
+
Batches of at most four rows dispatch a skinny-batch GEMV (one simdgroup per
|
| 179 |
+
output column, decoded weight segments reused across the batch); larger
|
| 180 |
+
batches dispatch the simdgroup-matrix tiles. The packed weight payload, row
|
| 181 |
+
norms, and centroids occupy about 25% of the materialized FP16 weight size at
|
| 182 |
+
W4. The resident reference excludes weight materialization time and retains
|
| 183 |
+
the complete FP16 matrix in memory — it is the throughput ceiling for a
|
| 184 |
+
kernel that decodes weights on the fly, not a like-for-like memory
|
| 185 |
+
configuration.
|
| 186 |
+
|
| 187 |
+
End-to-end FLUX.2 Klein 9B measurements and the SDNQ comparison are recorded in
|
| 188 |
+
[`docs/flux2-klein-9b-sdnq-vs-orbitquant.md`](../../docs/flux2-klein-9b-sdnq-vs-orbitquant.md).
|
benchmarks/benchmark.py
CHANGED
|
@@ -2,6 +2,9 @@ from __future__ import annotations
|
|
| 2 |
|
| 3 |
import argparse
|
| 4 |
import json
|
|
|
|
|
|
|
|
|
|
| 5 |
import time
|
| 6 |
|
| 7 |
import torch
|
|
@@ -28,48 +31,57 @@ def _synchronize(device: str) -> None:
|
|
| 28 |
torch.mps.synchronize()
|
| 29 |
|
| 30 |
|
| 31 |
-
def _time_call(device: str,
|
| 32 |
_synchronize(device)
|
| 33 |
-
start = time.
|
| 34 |
-
|
| 35 |
-
fn()
|
| 36 |
_synchronize(device)
|
| 37 |
-
return (time.
|
| 38 |
|
| 39 |
|
| 40 |
-
def
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
parser.add_argument("--with-bias", action="store_true")
|
| 51 |
-
args = parser.parse_args()
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
bias = (
|
| 66 |
torch.randn(args.out_features, device=args.device, dtype=dtype)
|
| 67 |
if args.with_bias
|
| 68 |
else None
|
| 69 |
)
|
| 70 |
|
| 71 |
-
indices_device = indices.long().to(args.device)
|
| 72 |
-
|
| 73 |
def materialize_reference_weight() -> torch.Tensor:
|
| 74 |
return (row_norms[:, None] * centroids[indices_device]).to(dtype)
|
| 75 |
|
|
@@ -98,41 +110,79 @@ def main() -> None:
|
|
| 98 |
def dequantize_then_linear_call() -> torch.Tensor:
|
| 99 |
return torch.nn.functional.linear(x, materialize_reference_weight(), bias)
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
for _ in range(args.warmup):
|
| 102 |
packed_call()
|
| 103 |
predequantized_linear_call()
|
| 104 |
dequantize_then_linear_call()
|
| 105 |
-
|
| 106 |
-
|
| 107 |
args.device,
|
| 108 |
args.iters,
|
| 109 |
predequantized_linear_call,
|
| 110 |
)
|
| 111 |
-
|
| 112 |
args.device,
|
| 113 |
args.iters,
|
| 114 |
dequantize_then_linear_call,
|
| 115 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
packed_output = packed_call()
|
| 118 |
reference_output = predequantized_linear_call()
|
| 119 |
_synchronize(args.device)
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
-
|
| 123 |
"device": args.device,
|
| 124 |
-
"device_name":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
"dtype": str(dtype).replace("torch.", ""),
|
| 126 |
"bits": args.bits,
|
| 127 |
-
"rows":
|
| 128 |
"in_features": args.in_features,
|
| 129 |
"out_features": args.out_features,
|
| 130 |
"iters": args.iters,
|
| 131 |
"warmup": args.warmup,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
"with_bias": args.with_bias,
|
| 133 |
"packed_seconds_per_iter": packed_seconds,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
"predequantized_f_linear_seconds_per_iter": predequantized_linear_seconds,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
"dequantize_then_f_linear_seconds_per_iter": dequantize_then_linear_seconds,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
"packed_weight_indices_bytes": packed_weight_indices_bytes,
|
| 137 |
"row_norms_bytes": row_norms_bytes,
|
| 138 |
"centroid_bytes": centroid_bytes,
|
|
@@ -155,6 +205,9 @@ def main() -> None:
|
|
| 155 |
if packed_seconds > 0
|
| 156 |
else None,
|
| 157 |
"max_abs_error": max_abs_error,
|
|
|
|
|
|
|
|
|
|
| 158 |
"reference": (
|
| 159 |
"predequantized PyTorch F.linear over a materialized dequantized "
|
| 160 |
"weight matrix"
|
|
@@ -163,7 +216,64 @@ def main() -> None:
|
|
| 163 |
"materialize the dequantized weight matrix, then call PyTorch F.linear"
|
| 164 |
),
|
| 165 |
}
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
|
| 169 |
if __name__ == "__main__":
|
|
|
|
| 2 |
|
| 3 |
import argparse
|
| 4 |
import json
|
| 5 |
+
import os
|
| 6 |
+
import platform
|
| 7 |
+
import statistics
|
| 8 |
import time
|
| 9 |
|
| 10 |
import torch
|
|
|
|
| 31 |
torch.mps.synchronize()
|
| 32 |
|
| 33 |
|
| 34 |
+
def _time_call(device: str, fn) -> float:
|
| 35 |
_synchronize(device)
|
| 36 |
+
start = time.perf_counter_ns()
|
| 37 |
+
fn()
|
|
|
|
| 38 |
_synchronize(device)
|
| 39 |
+
return (time.perf_counter_ns() - start) / 1_000_000_000
|
| 40 |
|
| 41 |
|
| 42 |
+
def _time_distribution(device: str, iters: int, fn) -> dict[str, float]:
|
| 43 |
+
samples = []
|
| 44 |
+
for _ in range(iters):
|
| 45 |
+
samples.append(_time_call(device, fn))
|
| 46 |
+
samples.sort()
|
| 47 |
+
return {
|
| 48 |
+
"mean": statistics.fmean(samples),
|
| 49 |
+
"median": statistics.median(samples),
|
| 50 |
+
"p95": samples[min(len(samples) - 1, int(len(samples) * 0.95))],
|
| 51 |
+
}
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
|
| 54 |
+
def _parse_rows(raw: str) -> list[int]:
|
| 55 |
+
rows_values = []
|
| 56 |
+
for chunk in raw.split(","):
|
| 57 |
+
chunk = chunk.strip()
|
| 58 |
+
if not chunk:
|
| 59 |
+
continue
|
| 60 |
+
rows = int(chunk)
|
| 61 |
+
if rows <= 0:
|
| 62 |
+
raise argparse.ArgumentTypeError("--rows values must be positive")
|
| 63 |
+
rows_values.append(rows)
|
| 64 |
+
if not rows_values:
|
| 65 |
+
raise argparse.ArgumentTypeError("--rows must list at least one row count")
|
| 66 |
+
return rows_values
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
_DTYPES = {
|
| 70 |
+
"float32": torch.float32,
|
| 71 |
+
"float16": torch.float16,
|
| 72 |
+
"bfloat16": torch.bfloat16,
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _benchmark_rows(args, rows: int, dtype: torch.dtype, weights) -> dict:
|
| 77 |
+
packed, row_norms, centroids, indices_device = weights
|
| 78 |
+
x = torch.randn(rows, args.in_features, device=args.device, dtype=dtype)
|
| 79 |
bias = (
|
| 80 |
torch.randn(args.out_features, device=args.device, dtype=dtype)
|
| 81 |
if args.with_bias
|
| 82 |
else None
|
| 83 |
)
|
| 84 |
|
|
|
|
|
|
|
| 85 |
def materialize_reference_weight() -> torch.Tensor:
|
| 86 |
return (row_norms[:, None] * centroids[indices_device]).to(dtype)
|
| 87 |
|
|
|
|
| 110 |
def dequantize_then_linear_call() -> torch.Tensor:
|
| 111 |
return torch.nn.functional.linear(x, materialize_reference_weight(), bias)
|
| 112 |
|
| 113 |
+
packed_first_call_seconds = _time_call(args.device, packed_call)
|
| 114 |
+
predequantized_first_call_seconds = _time_call(args.device, predequantized_linear_call)
|
| 115 |
+
dequantize_then_first_call_seconds = _time_call(args.device, dequantize_then_linear_call)
|
| 116 |
+
|
| 117 |
for _ in range(args.warmup):
|
| 118 |
packed_call()
|
| 119 |
predequantized_linear_call()
|
| 120 |
dequantize_then_linear_call()
|
| 121 |
+
packed_distribution = _time_distribution(args.device, args.iters, packed_call)
|
| 122 |
+
predequantized_distribution = _time_distribution(
|
| 123 |
args.device,
|
| 124 |
args.iters,
|
| 125 |
predequantized_linear_call,
|
| 126 |
)
|
| 127 |
+
dequantize_then_distribution = _time_distribution(
|
| 128 |
args.device,
|
| 129 |
args.iters,
|
| 130 |
dequantize_then_linear_call,
|
| 131 |
)
|
| 132 |
+
# Headline numbers are hot-loop medians; the mean is retained alongside the
|
| 133 |
+
# median/p95 so noisy first-iteration outliers cannot skew comparisons.
|
| 134 |
+
packed_seconds = packed_distribution["median"]
|
| 135 |
+
predequantized_linear_seconds = predequantized_distribution["median"]
|
| 136 |
+
dequantize_then_linear_seconds = dequantize_then_distribution["median"]
|
| 137 |
|
| 138 |
packed_output = packed_call()
|
| 139 |
reference_output = predequantized_linear_call()
|
| 140 |
_synchronize(args.device)
|
| 141 |
+
error = packed_output.float() - reference_output.float()
|
| 142 |
+
max_abs_error = error.abs().max().item()
|
| 143 |
+
rmse = error.square().mean().sqrt().item()
|
| 144 |
+
reference_rms = reference_output.float().square().mean().sqrt().item()
|
| 145 |
+
relative_rmse = rmse / max(reference_rms, 1e-12)
|
| 146 |
|
| 147 |
+
return {
|
| 148 |
"device": args.device,
|
| 149 |
+
"device_name": (
|
| 150 |
+
torch.cuda.get_device_name(0)
|
| 151 |
+
if args.device == "cuda"
|
| 152 |
+
else "mps"
|
| 153 |
+
if args.device == "mps"
|
| 154 |
+
else f"{platform.processor() or platform.machine()} "
|
| 155 |
+
f"({torch.backends.cpu.get_cpu_capability()})"
|
| 156 |
+
),
|
| 157 |
"dtype": str(dtype).replace("torch.", ""),
|
| 158 |
"bits": args.bits,
|
| 159 |
+
"rows": rows,
|
| 160 |
"in_features": args.in_features,
|
| 161 |
"out_features": args.out_features,
|
| 162 |
"iters": args.iters,
|
| 163 |
"warmup": args.warmup,
|
| 164 |
+
"threads": (
|
| 165 |
+
os.environ.get("ORBITQUANT_CPU_THREADS", "runtime default")
|
| 166 |
+
if args.device == "cpu"
|
| 167 |
+
else None
|
| 168 |
+
),
|
| 169 |
+
"torch_threads": torch.get_num_threads() if args.device == "cpu" else None,
|
| 170 |
"with_bias": args.with_bias,
|
| 171 |
"packed_seconds_per_iter": packed_seconds,
|
| 172 |
+
"packed_first_call_seconds": packed_first_call_seconds,
|
| 173 |
+
"packed_hot_mean_seconds": packed_distribution["mean"],
|
| 174 |
+
"packed_hot_median_seconds": packed_distribution["median"],
|
| 175 |
+
"packed_hot_p95_seconds": packed_distribution["p95"],
|
| 176 |
"predequantized_f_linear_seconds_per_iter": predequantized_linear_seconds,
|
| 177 |
+
"predequantized_first_call_seconds": predequantized_first_call_seconds,
|
| 178 |
+
"predequantized_hot_mean_seconds": predequantized_distribution["mean"],
|
| 179 |
+
"predequantized_hot_median_seconds": predequantized_distribution["median"],
|
| 180 |
+
"predequantized_hot_p95_seconds": predequantized_distribution["p95"],
|
| 181 |
"dequantize_then_f_linear_seconds_per_iter": dequantize_then_linear_seconds,
|
| 182 |
+
"dequantize_then_first_call_seconds": dequantize_then_first_call_seconds,
|
| 183 |
+
"dequantize_then_hot_mean_seconds": dequantize_then_distribution["mean"],
|
| 184 |
+
"dequantize_then_hot_median_seconds": dequantize_then_distribution["median"],
|
| 185 |
+
"dequantize_then_hot_p95_seconds": dequantize_then_distribution["p95"],
|
| 186 |
"packed_weight_indices_bytes": packed_weight_indices_bytes,
|
| 187 |
"row_norms_bytes": row_norms_bytes,
|
| 188 |
"centroid_bytes": centroid_bytes,
|
|
|
|
| 205 |
if packed_seconds > 0
|
| 206 |
else None,
|
| 207 |
"max_abs_error": max_abs_error,
|
| 208 |
+
"rmse": rmse,
|
| 209 |
+
"relative_rmse": relative_rmse,
|
| 210 |
+
"timing_headline": "hot-loop median seconds per iteration",
|
| 211 |
"reference": (
|
| 212 |
"predequantized PyTorch F.linear over a materialized dequantized "
|
| 213 |
"weight matrix"
|
|
|
|
| 216 |
"materialize the dequantized weight matrix, then call PyTorch F.linear"
|
| 217 |
),
|
| 218 |
}
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def main() -> None:
|
| 222 |
+
parser = argparse.ArgumentParser()
|
| 223 |
+
parser.add_argument("--device", choices=["cpu", "cuda", "mps"], default="cuda")
|
| 224 |
+
parser.add_argument("--bits", type=int, default=4)
|
| 225 |
+
parser.add_argument(
|
| 226 |
+
"--rows",
|
| 227 |
+
type=_parse_rows,
|
| 228 |
+
default=[1, 8, 512, 4096],
|
| 229 |
+
help="comma-separated row counts to sweep (default covers decode-bound "
|
| 230 |
+
"small batches and GEMM-bound large batches)",
|
| 231 |
+
)
|
| 232 |
+
parser.add_argument(
|
| 233 |
+
"--dtype",
|
| 234 |
+
choices=["auto", *sorted(_DTYPES)],
|
| 235 |
+
default="auto",
|
| 236 |
+
help="activation dtype; auto picks float16 on mps and bfloat16 elsewhere",
|
| 237 |
+
)
|
| 238 |
+
parser.add_argument("--in-features", type=int, default=3072)
|
| 239 |
+
parser.add_argument("--out-features", type=int, default=3072)
|
| 240 |
+
parser.add_argument("--iters", type=int, default=20)
|
| 241 |
+
parser.add_argument("--warmup", type=int, default=3)
|
| 242 |
+
parser.add_argument("--seed", type=int, default=0)
|
| 243 |
+
parser.add_argument("--threads", type=int, default=0)
|
| 244 |
+
parser.add_argument("--with-bias", action="store_true")
|
| 245 |
+
args = parser.parse_args()
|
| 246 |
+
|
| 247 |
+
if args.threads < 0:
|
| 248 |
+
parser.error("--threads must be non-negative")
|
| 249 |
+
if args.iters <= 0 or args.warmup < 0:
|
| 250 |
+
parser.error("--iters must be positive and --warmup must be non-negative")
|
| 251 |
+
if args.device == "cpu" and args.threads > 0:
|
| 252 |
+
os.environ["ORBITQUANT_CPU_THREADS"] = str(args.threads)
|
| 253 |
+
torch.set_num_threads(args.threads)
|
| 254 |
+
|
| 255 |
+
torch.manual_seed(args.seed)
|
| 256 |
+
if args.dtype == "auto":
|
| 257 |
+
dtype = torch.float16 if args.device == "mps" else torch.bfloat16
|
| 258 |
+
else:
|
| 259 |
+
dtype = _DTYPES[args.dtype]
|
| 260 |
+
indices = torch.randint(
|
| 261 |
+
0,
|
| 262 |
+
2**args.bits,
|
| 263 |
+
(args.out_features, args.in_features),
|
| 264 |
+
dtype=torch.uint8,
|
| 265 |
+
)
|
| 266 |
+
packed = _pack(indices, args.bits).to(args.device)
|
| 267 |
+
row_norms = torch.ones(args.out_features, device=args.device, dtype=torch.bfloat16)
|
| 268 |
+
centroids = torch.linspace(-1.0, 1.0, 2**args.bits, device=args.device)
|
| 269 |
+
indices_device = indices.long().to(args.device)
|
| 270 |
+
weights = (packed, row_norms, centroids, indices_device)
|
| 271 |
+
|
| 272 |
+
payloads = [_benchmark_rows(args, rows, dtype, weights) for rows in args.rows]
|
| 273 |
+
if len(payloads) == 1:
|
| 274 |
+
print(json.dumps(payloads[0], indent=2, sort_keys=True))
|
| 275 |
+
else:
|
| 276 |
+
print(json.dumps(payloads, indent=2, sort_keys=True))
|
| 277 |
|
| 278 |
|
| 279 |
if __name__ == "__main__":
|
build.toml
CHANGED
|
@@ -3,7 +3,7 @@ name = "orbitquant-packed-matmul"
|
|
| 3 |
version = 1
|
| 4 |
edition = 5
|
| 5 |
license = "Apache-2.0"
|
| 6 |
-
backends = ["cuda", "metal"]
|
| 7 |
upstream = "https://github.com/iamwavecut/OrbitQuant"
|
| 8 |
source = "https://huggingface.co/WaveCut/orbitquant-packed-matmul"
|
| 9 |
|
|
@@ -16,6 +16,39 @@ src = [
|
|
| 16 |
"torch-ext/torch_binding.h",
|
| 17 |
]
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
[kernel.packed_matmul_cuda]
|
| 20 |
backend = "cuda"
|
| 21 |
depends = ["torch"]
|
|
|
|
| 3 |
version = 1
|
| 4 |
edition = 5
|
| 5 |
license = "Apache-2.0"
|
| 6 |
+
backends = ["cpu", "cuda", "metal"]
|
| 7 |
upstream = "https://github.com/iamwavecut/OrbitQuant"
|
| 8 |
source = "https://huggingface.co/WaveCut/orbitquant-packed-matmul"
|
| 9 |
|
|
|
|
| 16 |
"torch-ext/torch_binding.h",
|
| 17 |
]
|
| 18 |
|
| 19 |
+
[torch.stable-abi]
|
| 20 |
+
cpu = "2.11"
|
| 21 |
+
|
| 22 |
+
[kernel.packed_matmul_cpu]
|
| 23 |
+
backend = "cpu"
|
| 24 |
+
depends = ["torch"]
|
| 25 |
+
include = ["orbitquant_packed_matmul_cpu"]
|
| 26 |
+
src = [
|
| 27 |
+
"orbitquant_packed_matmul_cpu/cpu_isa.cpp",
|
| 28 |
+
"orbitquant_packed_matmul_cpu/cpu_kernel_args.h",
|
| 29 |
+
"orbitquant_packed_matmul_cpu/cpu_pool.cpp",
|
| 30 |
+
"orbitquant_packed_matmul_cpu/cpu_pool.h",
|
| 31 |
+
"orbitquant_packed_matmul_cpu/cpu_threads.cpp",
|
| 32 |
+
"orbitquant_packed_matmul_cpu/cpu_threads.h",
|
| 33 |
+
"orbitquant_packed_matmul_cpu/packed_adaln_cpu.cpp",
|
| 34 |
+
"orbitquant_packed_matmul_cpu/packed_matmul_cpu.cpp",
|
| 35 |
+
"orbitquant_packed_matmul_cpu/packed_matmul_cpu.h",
|
| 36 |
+
"orbitquant_packed_matmul_cpu/packed_matmul_scalar.cpp",
|
| 37 |
+
"orbitquant_packed_matmul_cpu/packed_matmul_neon.cpp",
|
| 38 |
+
"orbitquant_packed_matmul_cpu/packed_matmul_x86_avx512.cpp",
|
| 39 |
+
"orbitquant_packed_matmul_cpu/quantize_activations_cpu.cpp",
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
[kernel.packed_matmul_cpu_x86_avx2]
|
| 43 |
+
backend = "cpu"
|
| 44 |
+
depends = ["torch"]
|
| 45 |
+
include = ["orbitquant_packed_matmul_cpu"]
|
| 46 |
+
cxx-flags = ["$<$<CXX_COMPILER_ID:MSVC>:/arch:AVX2>"]
|
| 47 |
+
src = [
|
| 48 |
+
"orbitquant_packed_matmul_cpu/cpu_msvc_avx2.cpp",
|
| 49 |
+
"orbitquant_packed_matmul_cpu/packed_matmul_x86.cpp",
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
[kernel.packed_matmul_cuda]
|
| 53 |
backend = "cuda"
|
| 54 |
depends = ["torch"]
|
orbitquant_packed_matmul_cpu/cpu_isa.cpp
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "packed_matmul_cpu.h"
|
| 2 |
+
|
| 3 |
+
#if defined(__x86_64__) || defined(_M_X64)
|
| 4 |
+
#if defined(_MSC_VER)
|
| 5 |
+
#include <intrin.h>
|
| 6 |
+
#pragma intrinsic(_xgetbv)
|
| 7 |
+
#endif
|
| 8 |
+
#endif
|
| 9 |
+
|
| 10 |
+
namespace orbitquant::cpu {
|
| 11 |
+
namespace {
|
| 12 |
+
|
| 13 |
+
bool runtime_has_avx2_fma_f16c() {
|
| 14 |
+
#if defined(_MSC_VER) && defined(_M_X64)
|
| 15 |
+
int registers[4]{};
|
| 16 |
+
__cpuid(registers, 1);
|
| 17 |
+
const bool osxsave = (registers[2] & (1 << 27)) != 0;
|
| 18 |
+
const bool avx = (registers[2] & (1 << 28)) != 0;
|
| 19 |
+
const bool fma = (registers[2] & (1 << 12)) != 0;
|
| 20 |
+
const bool f16c = (registers[2] & (1 << 29)) != 0;
|
| 21 |
+
if (!osxsave || !avx || !fma || !f16c || (_xgetbv(0) & 0x6) != 0x6) {
|
| 22 |
+
return false;
|
| 23 |
+
}
|
| 24 |
+
__cpuidex(registers, 7, 0);
|
| 25 |
+
return (registers[1] & (1 << 5)) != 0;
|
| 26 |
+
#elif defined(__x86_64__)
|
| 27 |
+
__builtin_cpu_init();
|
| 28 |
+
return __builtin_cpu_supports("avx2") && __builtin_cpu_supports("fma") &&
|
| 29 |
+
__builtin_cpu_supports("f16c");
|
| 30 |
+
#else
|
| 31 |
+
return false;
|
| 32 |
+
#endif
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
} // namespace
|
| 36 |
+
|
| 37 |
+
bool packed_matmul_x86_avx2_available() {
|
| 38 |
+
static const bool available = runtime_has_avx2_fma_f16c();
|
| 39 |
+
return available;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
} // namespace orbitquant::cpu
|
orbitquant_packed_matmul_cpu/cpu_kernel_args.h
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
#include "packed_matmul_cpu.h"
|
| 4 |
+
|
| 5 |
+
#include <cstdint>
|
| 6 |
+
|
| 7 |
+
namespace orbitquant::cpu {
|
| 8 |
+
|
| 9 |
+
enum class ActivationIsa : std::uint8_t {
|
| 10 |
+
Portable,
|
| 11 |
+
Neon,
|
| 12 |
+
Avx2,
|
| 13 |
+
Avx512,
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
struct ActivationArgs {
|
| 17 |
+
void *out;
|
| 18 |
+
void const *x;
|
| 19 |
+
// Exactly one of the permutation pointers is set (int64 checkpoints and the
|
| 20 |
+
// int32 hot-path constants are both accepted).
|
| 21 |
+
std::int64_t const *permutation;
|
| 22 |
+
std::int32_t const *permutation_i32;
|
| 23 |
+
std::int8_t const *signs;
|
| 24 |
+
float const *centroids;
|
| 25 |
+
float const *boundaries;
|
| 26 |
+
ScalarKind scalar_kind;
|
| 27 |
+
ActivationIsa isa;
|
| 28 |
+
std::int64_t rows;
|
| 29 |
+
std::int64_t dim;
|
| 30 |
+
std::int64_t boundary_count;
|
| 31 |
+
std::int64_t block_size;
|
| 32 |
+
float eps;
|
| 33 |
+
float inv_sqrt_block;
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
+
void activation_fwht_msvc_avx2(float *values, std::int64_t block_size);
|
| 37 |
+
|
| 38 |
+
float activation_squared_norm_msvc_avx2(
|
| 39 |
+
void const *data,
|
| 40 |
+
ScalarKind scalar_kind,
|
| 41 |
+
std::int64_t offset,
|
| 42 |
+
std::int64_t dim);
|
| 43 |
+
|
| 44 |
+
void activation_quantize_lookup_msvc_avx2(
|
| 45 |
+
ActivationArgs const &args,
|
| 46 |
+
float const *scratch,
|
| 47 |
+
std::int64_t output_offset,
|
| 48 |
+
float norm);
|
| 49 |
+
|
| 50 |
+
struct AdalnArgs {
|
| 51 |
+
void *out;
|
| 52 |
+
void const *x;
|
| 53 |
+
std::uint8_t const *packed_weight;
|
| 54 |
+
float const *scales;
|
| 55 |
+
float const *bias;
|
| 56 |
+
bool has_bias;
|
| 57 |
+
std::int64_t rows;
|
| 58 |
+
std::int64_t out_features;
|
| 59 |
+
std::int64_t in_features;
|
| 60 |
+
std::int64_t group_size;
|
| 61 |
+
std::int64_t num_groups;
|
| 62 |
+
std::int64_t padded_in_features;
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
using AdalnRangeFn = void (*)(
|
| 66 |
+
AdalnArgs const &args,
|
| 67 |
+
std::int64_t out_start,
|
| 68 |
+
std::int64_t out_end);
|
| 69 |
+
|
| 70 |
+
void packed_adaln_msvc_avx2_range(
|
| 71 |
+
AdalnArgs const &args,
|
| 72 |
+
std::int64_t out_start,
|
| 73 |
+
std::int64_t out_end);
|
| 74 |
+
|
| 75 |
+
} // namespace orbitquant::cpu
|
orbitquant_packed_matmul_cpu/cpu_msvc_avx2.cpp
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "cpu_kernel_args.h"
|
| 2 |
+
|
| 3 |
+
#if defined(_MSC_VER) && defined(_M_X64)
|
| 4 |
+
#include <immintrin.h>
|
| 5 |
+
|
| 6 |
+
#include <torch/headeronly/util/BFloat16.h>
|
| 7 |
+
#include <torch/headeronly/util/Half.h>
|
| 8 |
+
|
| 9 |
+
#include <algorithm>
|
| 10 |
+
#include <cstdint>
|
| 11 |
+
#include <cstring>
|
| 12 |
+
#include <type_traits>
|
| 13 |
+
|
| 14 |
+
namespace orbitquant::cpu {
|
| 15 |
+
namespace {
|
| 16 |
+
|
| 17 |
+
template <typename scalar_t>
|
| 18 |
+
inline float load_scalar(void const *data, std::int64_t offset) {
|
| 19 |
+
return static_cast<float>(static_cast<scalar_t const *>(data)[offset]);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
template <>
|
| 23 |
+
inline float load_scalar<float>(void const *data, std::int64_t offset) {
|
| 24 |
+
return static_cast<float const *>(data)[offset];
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
template <typename scalar_t>
|
| 28 |
+
inline void store_scalar(void *data, std::int64_t offset, float value) {
|
| 29 |
+
static_cast<scalar_t *>(data)[offset] = scalar_t(value);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
template <>
|
| 33 |
+
inline void store_scalar<float>(void *data, std::int64_t offset, float value) {
|
| 34 |
+
static_cast<float *>(data)[offset] = value;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
template <typename scalar_t>
|
| 38 |
+
float squared_norm_avx2(
|
| 39 |
+
void const *data,
|
| 40 |
+
std::int64_t offset,
|
| 41 |
+
std::int64_t dim) {
|
| 42 |
+
__m256 accumulator = _mm256_setzero_ps();
|
| 43 |
+
std::int64_t index = 0;
|
| 44 |
+
for (; index + 8 <= dim; index += 8) {
|
| 45 |
+
__m256 values;
|
| 46 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 47 |
+
values = _mm256_loadu_ps(
|
| 48 |
+
static_cast<float const *>(data) + offset + index);
|
| 49 |
+
} else if constexpr (std::is_same_v<scalar_t, c10::Half>) {
|
| 50 |
+
const auto *source =
|
| 51 |
+
static_cast<std::uint16_t const *>(data) + offset + index;
|
| 52 |
+
values = _mm256_cvtph_ps(
|
| 53 |
+
_mm_loadu_si128(reinterpret_cast<__m128i const *>(source)));
|
| 54 |
+
} else {
|
| 55 |
+
const auto *source =
|
| 56 |
+
static_cast<std::uint16_t const *>(data) + offset + index;
|
| 57 |
+
const __m128i packed =
|
| 58 |
+
_mm_loadu_si128(reinterpret_cast<__m128i const *>(source));
|
| 59 |
+
values = _mm256_castsi256_ps(
|
| 60 |
+
_mm256_slli_epi32(_mm256_cvtepu16_epi32(packed), 16));
|
| 61 |
+
}
|
| 62 |
+
accumulator = _mm256_fmadd_ps(values, values, accumulator);
|
| 63 |
+
}
|
| 64 |
+
const __m128 halves = _mm_add_ps(
|
| 65 |
+
_mm256_castps256_ps128(accumulator),
|
| 66 |
+
_mm256_extractf128_ps(accumulator, 1));
|
| 67 |
+
const __m128 pairs = _mm_hadd_ps(halves, halves);
|
| 68 |
+
float result = _mm_cvtss_f32(_mm_hadd_ps(pairs, pairs));
|
| 69 |
+
for (; index < dim; ++index) {
|
| 70 |
+
const float value = load_scalar<scalar_t>(data, offset + index);
|
| 71 |
+
result += value * value;
|
| 72 |
+
}
|
| 73 |
+
return result;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
inline std::int64_t nearest_centroid(
|
| 77 |
+
float value,
|
| 78 |
+
float const *boundaries,
|
| 79 |
+
std::int64_t boundary_count) {
|
| 80 |
+
std::int64_t low = 0;
|
| 81 |
+
std::int64_t high = boundary_count;
|
| 82 |
+
while (low < high) {
|
| 83 |
+
const std::int64_t middle = low + (high - low) / 2;
|
| 84 |
+
if (value <= boundaries[middle]) {
|
| 85 |
+
high = middle;
|
| 86 |
+
} else {
|
| 87 |
+
low = middle + 1;
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
return low;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
inline __m128i float8_to_bfloat8(__m256 values) {
|
| 94 |
+
const __m256i bits = _mm256_castps_si256(values);
|
| 95 |
+
const __m256i absolute_bits =
|
| 96 |
+
_mm256_and_si256(bits, _mm256_set1_epi32(0x7fffffff));
|
| 97 |
+
const __m256i nan_mask =
|
| 98 |
+
_mm256_cmpgt_epi32(absolute_bits, _mm256_set1_epi32(0x7f800000));
|
| 99 |
+
const __m256i rounding = _mm256_add_epi32(
|
| 100 |
+
_mm256_set1_epi32(0x7fff),
|
| 101 |
+
_mm256_and_si256(_mm256_srli_epi32(bits, 16), _mm256_set1_epi32(1)));
|
| 102 |
+
const __m256i upper = _mm256_srli_epi32(
|
| 103 |
+
_mm256_add_epi32(bits, rounding),
|
| 104 |
+
16);
|
| 105 |
+
__m128i packed = _mm_packus_epi32(
|
| 106 |
+
_mm256_castsi256_si128(upper),
|
| 107 |
+
_mm256_extracti128_si256(upper, 1));
|
| 108 |
+
const __m128i packed_nan_mask = _mm_packs_epi32(
|
| 109 |
+
_mm256_castsi256_si128(nan_mask),
|
| 110 |
+
_mm256_extracti128_si256(nan_mask, 1));
|
| 111 |
+
return _mm_blendv_epi8(
|
| 112 |
+
packed,
|
| 113 |
+
_mm_set1_epi16(0x7fc0),
|
| 114 |
+
packed_nan_mask);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
template <typename scalar_t>
|
| 118 |
+
void quantize_lookup_avx2(
|
| 119 |
+
ActivationArgs const &args,
|
| 120 |
+
float const *scratch,
|
| 121 |
+
std::int64_t output_offset,
|
| 122 |
+
float norm) {
|
| 123 |
+
const __m256 inverse_sqrt_block = _mm256_set1_ps(args.inv_sqrt_block);
|
| 124 |
+
const __m256 output_norm = _mm256_set1_ps(norm);
|
| 125 |
+
const __m256i ones = _mm256_set1_epi32(1);
|
| 126 |
+
std::int64_t index = 0;
|
| 127 |
+
for (; index + 8 <= args.dim; index += 8) {
|
| 128 |
+
const __m256 direction = _mm256_mul_ps(
|
| 129 |
+
_mm256_loadu_ps(scratch + index), inverse_sqrt_block);
|
| 130 |
+
__m256i centroid_indices = _mm256_setzero_si256();
|
| 131 |
+
for (std::int64_t boundary = 0; boundary < args.boundary_count; ++boundary) {
|
| 132 |
+
const __m256 comparison = _mm256_cmp_ps(
|
| 133 |
+
direction,
|
| 134 |
+
_mm256_set1_ps(args.boundaries[boundary]),
|
| 135 |
+
_CMP_NLE_UQ);
|
| 136 |
+
centroid_indices = _mm256_add_epi32(
|
| 137 |
+
centroid_indices,
|
| 138 |
+
_mm256_and_si256(_mm256_castps_si256(comparison), ones));
|
| 139 |
+
}
|
| 140 |
+
const __m256 output = _mm256_mul_ps(
|
| 141 |
+
_mm256_i32gather_ps(args.centroids, centroid_indices, 4),
|
| 142 |
+
output_norm);
|
| 143 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 144 |
+
_mm256_storeu_ps(
|
| 145 |
+
static_cast<float *>(args.out) + output_offset + index,
|
| 146 |
+
output);
|
| 147 |
+
} else if constexpr (std::is_same_v<scalar_t, c10::Half>) {
|
| 148 |
+
_mm_storeu_si128(
|
| 149 |
+
reinterpret_cast<__m128i *>(
|
| 150 |
+
static_cast<std::uint16_t *>(args.out) + output_offset + index),
|
| 151 |
+
_mm256_cvtps_ph(output, _MM_FROUND_TO_NEAREST_INT));
|
| 152 |
+
} else {
|
| 153 |
+
_mm_storeu_si128(
|
| 154 |
+
reinterpret_cast<__m128i *>(
|
| 155 |
+
static_cast<std::uint16_t *>(args.out) + output_offset + index),
|
| 156 |
+
float8_to_bfloat8(output));
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
for (; index < args.dim; ++index) {
|
| 160 |
+
const float direction = scratch[index] * args.inv_sqrt_block;
|
| 161 |
+
const std::int64_t centroid_index = nearest_centroid(
|
| 162 |
+
direction, args.boundaries, args.boundary_count);
|
| 163 |
+
store_scalar<scalar_t>(
|
| 164 |
+
args.out,
|
| 165 |
+
output_offset + index,
|
| 166 |
+
args.centroids[centroid_index] * norm);
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
inline float load_bfloat(void const *data, std::int64_t offset) {
|
| 171 |
+
return static_cast<float>(
|
| 172 |
+
static_cast<c10::BFloat16 const *>(data)[offset]);
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
inline void store_bfloat(void *data, std::int64_t offset, float value) {
|
| 176 |
+
static_cast<c10::BFloat16 *>(data)[offset] = c10::BFloat16(value);
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
inline std::uint8_t unpack_adaln_index(
|
| 180 |
+
std::uint8_t const *packed,
|
| 181 |
+
std::int64_t flat_index) {
|
| 182 |
+
const std::uint8_t byte = packed[flat_index / 2];
|
| 183 |
+
return (flat_index & 1) == 0 ? byte & 15u : (byte >> 4) & 15u;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
inline float dequantized_adaln_value(std::uint8_t index, float scale) {
|
| 187 |
+
return static_cast<float>(
|
| 188 |
+
c10::BFloat16((static_cast<int>(index) - 8) * scale));
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
inline void fill_group_lut(float *lut, float scale) {
|
| 192 |
+
for (int index = 0; index < 16; ++index) {
|
| 193 |
+
lut[index] = dequantized_adaln_value(
|
| 194 |
+
static_cast<std::uint8_t>(index), scale);
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
void packed_adaln_scalar_range(
|
| 199 |
+
AdalnArgs const &args,
|
| 200 |
+
std::int64_t out_start,
|
| 201 |
+
std::int64_t out_end) {
|
| 202 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 203 |
+
for (std::int64_t row = 0; row < args.rows; ++row) {
|
| 204 |
+
float accumulator = 0.0f;
|
| 205 |
+
const std::int64_t input_offset = row * args.in_features;
|
| 206 |
+
for (std::int64_t k = 0; k < args.in_features; ++k) {
|
| 207 |
+
const std::int64_t weight_offset =
|
| 208 |
+
out_col * args.padded_in_features + k;
|
| 209 |
+
const std::uint8_t index =
|
| 210 |
+
unpack_adaln_index(args.packed_weight, weight_offset);
|
| 211 |
+
const float scale =
|
| 212 |
+
args.scales[out_col * args.num_groups + k / args.group_size];
|
| 213 |
+
accumulator += load_bfloat(args.x, input_offset + k) *
|
| 214 |
+
dequantized_adaln_value(index, scale);
|
| 215 |
+
}
|
| 216 |
+
if (args.has_bias) {
|
| 217 |
+
accumulator += args.bias[out_col];
|
| 218 |
+
}
|
| 219 |
+
store_bfloat(
|
| 220 |
+
args.out,
|
| 221 |
+
row * args.out_features + out_col,
|
| 222 |
+
accumulator);
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
inline __m256 load_bfloat8(void const *data, std::int64_t offset) {
|
| 228 |
+
const auto *source =
|
| 229 |
+
static_cast<std::uint16_t const *>(data) + offset;
|
| 230 |
+
const __m128i packed =
|
| 231 |
+
_mm_loadu_si128(reinterpret_cast<__m128i const *>(source));
|
| 232 |
+
return _mm256_castsi256_ps(
|
| 233 |
+
_mm256_slli_epi32(_mm256_cvtepu16_epi32(packed), 16));
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
inline float horizontal_sum(__m256 value) {
|
| 237 |
+
const __m128 halves = _mm_add_ps(
|
| 238 |
+
_mm256_castps256_ps128(value),
|
| 239 |
+
_mm256_extractf128_ps(value, 1));
|
| 240 |
+
const __m128 pairs = _mm_hadd_ps(halves, halves);
|
| 241 |
+
return _mm_cvtss_f32(_mm_hadd_ps(pairs, pairs));
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
template <int row_tile>
|
| 245 |
+
inline void packed_adaln_avx2_rows(
|
| 246 |
+
AdalnArgs const &args,
|
| 247 |
+
std::uint8_t const *packed_row,
|
| 248 |
+
float const *scale_row,
|
| 249 |
+
std::int64_t out_col,
|
| 250 |
+
std::int64_t row_start) {
|
| 251 |
+
__m256 accumulators[row_tile];
|
| 252 |
+
float tails[row_tile]{};
|
| 253 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 254 |
+
accumulators[row] = _mm256_setzero_ps();
|
| 255 |
+
}
|
| 256 |
+
const __m128i nibble_mask = _mm_set1_epi8(15);
|
| 257 |
+
const __m256i low_table_limit = _mm256_set1_epi32(7);
|
| 258 |
+
|
| 259 |
+
for (std::int64_t group = 0; group < args.num_groups; ++group) {
|
| 260 |
+
float lut[16];
|
| 261 |
+
fill_group_lut(lut, scale_row[group]);
|
| 262 |
+
const __m256 lut_low = _mm256_loadu_ps(lut);
|
| 263 |
+
const __m256 lut_high = _mm256_loadu_ps(lut + 8);
|
| 264 |
+
const std::int64_t group_start = group * args.group_size;
|
| 265 |
+
const std::int64_t group_end = std::min(
|
| 266 |
+
args.in_features,
|
| 267 |
+
group_start + args.group_size);
|
| 268 |
+
std::int64_t k = group_start;
|
| 269 |
+
for (; k + 8 <= group_end; k += 8) {
|
| 270 |
+
std::int32_t packed;
|
| 271 |
+
std::memcpy(&packed, packed_row + k / 2, sizeof(packed));
|
| 272 |
+
const __m128i bytes = _mm_cvtsi32_si128(packed);
|
| 273 |
+
const __m128i low = _mm_and_si128(bytes, nibble_mask);
|
| 274 |
+
const __m128i high = _mm_and_si128(
|
| 275 |
+
_mm_srli_epi16(bytes, 4), nibble_mask);
|
| 276 |
+
const __m256i indices =
|
| 277 |
+
_mm256_cvtepu8_epi32(_mm_unpacklo_epi8(low, high));
|
| 278 |
+
const __m256 weight = _mm256_blendv_ps(
|
| 279 |
+
_mm256_permutevar8x32_ps(lut_low, indices),
|
| 280 |
+
_mm256_permutevar8x32_ps(lut_high, indices),
|
| 281 |
+
_mm256_castsi256_ps(
|
| 282 |
+
_mm256_cmpgt_epi32(indices, low_table_limit)));
|
| 283 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 284 |
+
const std::int64_t input_offset =
|
| 285 |
+
(row_start + row) * args.in_features + k;
|
| 286 |
+
accumulators[row] = _mm256_fmadd_ps(
|
| 287 |
+
load_bfloat8(args.x, input_offset),
|
| 288 |
+
weight,
|
| 289 |
+
accumulators[row]);
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
for (; k < group_end; ++k) {
|
| 293 |
+
const float weight = lut[unpack_adaln_index(packed_row, k)];
|
| 294 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 295 |
+
tails[row] += load_bfloat(
|
| 296 |
+
args.x,
|
| 297 |
+
(row_start + row) * args.in_features + k) * weight;
|
| 298 |
+
}
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 303 |
+
float accumulator = horizontal_sum(accumulators[row]) + tails[row];
|
| 304 |
+
if (args.has_bias) {
|
| 305 |
+
accumulator += args.bias[out_col];
|
| 306 |
+
}
|
| 307 |
+
store_bfloat(
|
| 308 |
+
args.out,
|
| 309 |
+
(row_start + row) * args.out_features + out_col,
|
| 310 |
+
accumulator);
|
| 311 |
+
}
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
template <
|
| 315 |
+
void (*rows8)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t),
|
| 316 |
+
void (*rows4)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t),
|
| 317 |
+
void (*rows3)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t),
|
| 318 |
+
void (*rows2)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t),
|
| 319 |
+
void (*rows1)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t)>
|
| 320 |
+
void packed_adaln_tiled_range(
|
| 321 |
+
AdalnArgs const &args,
|
| 322 |
+
std::int64_t out_start,
|
| 323 |
+
std::int64_t out_end) {
|
| 324 |
+
const std::int64_t packed_row_bytes = args.padded_in_features / 2;
|
| 325 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 326 |
+
const auto *packed_row =
|
| 327 |
+
args.packed_weight + out_col * packed_row_bytes;
|
| 328 |
+
const auto *scale_row =
|
| 329 |
+
args.scales + out_col * args.num_groups;
|
| 330 |
+
std::int64_t row = 0;
|
| 331 |
+
for (; row + 8 <= args.rows; row += 8) {
|
| 332 |
+
rows8(args, packed_row, scale_row, out_col, row);
|
| 333 |
+
}
|
| 334 |
+
if (row + 4 <= args.rows) {
|
| 335 |
+
rows4(args, packed_row, scale_row, out_col, row);
|
| 336 |
+
row += 4;
|
| 337 |
+
}
|
| 338 |
+
switch (args.rows - row) {
|
| 339 |
+
case 3:
|
| 340 |
+
rows3(args, packed_row, scale_row, out_col, row);
|
| 341 |
+
break;
|
| 342 |
+
case 2:
|
| 343 |
+
rows2(args, packed_row, scale_row, out_col, row);
|
| 344 |
+
break;
|
| 345 |
+
case 1:
|
| 346 |
+
rows1(args, packed_row, scale_row, out_col, row);
|
| 347 |
+
break;
|
| 348 |
+
default:
|
| 349 |
+
break;
|
| 350 |
+
}
|
| 351 |
+
}
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
} // namespace
|
| 355 |
+
|
| 356 |
+
void activation_fwht_msvc_avx2(float *values, std::int64_t block_size) {
|
| 357 |
+
for (std::int64_t half = 1; half < block_size; half *= 2) {
|
| 358 |
+
for (std::int64_t base = 0; base < block_size; base += 2 * half) {
|
| 359 |
+
std::int64_t offset = 0;
|
| 360 |
+
for (; offset + 8 <= half; offset += 8) {
|
| 361 |
+
const __m256 left = _mm256_loadu_ps(values + base + offset);
|
| 362 |
+
const __m256 right =
|
| 363 |
+
_mm256_loadu_ps(values + base + half + offset);
|
| 364 |
+
_mm256_storeu_ps(values + base + offset, _mm256_add_ps(left, right));
|
| 365 |
+
_mm256_storeu_ps(
|
| 366 |
+
values + base + half + offset,
|
| 367 |
+
_mm256_sub_ps(left, right));
|
| 368 |
+
}
|
| 369 |
+
for (; offset < half; ++offset) {
|
| 370 |
+
const float left = values[base + offset];
|
| 371 |
+
const float right = values[base + half + offset];
|
| 372 |
+
values[base + offset] = left + right;
|
| 373 |
+
values[base + half + offset] = left - right;
|
| 374 |
+
}
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
float activation_squared_norm_msvc_avx2(
|
| 380 |
+
void const *data,
|
| 381 |
+
ScalarKind scalar_kind,
|
| 382 |
+
std::int64_t offset,
|
| 383 |
+
std::int64_t dim) {
|
| 384 |
+
switch (scalar_kind) {
|
| 385 |
+
case ScalarKind::Float32:
|
| 386 |
+
return squared_norm_avx2<float>(data, offset, dim);
|
| 387 |
+
case ScalarKind::Float16:
|
| 388 |
+
return squared_norm_avx2<c10::Half>(data, offset, dim);
|
| 389 |
+
case ScalarKind::BFloat16:
|
| 390 |
+
return squared_norm_avx2<c10::BFloat16>(data, offset, dim);
|
| 391 |
+
}
|
| 392 |
+
return 0.0f;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
void activation_quantize_lookup_msvc_avx2(
|
| 396 |
+
ActivationArgs const &args,
|
| 397 |
+
float const *scratch,
|
| 398 |
+
std::int64_t output_offset,
|
| 399 |
+
float norm) {
|
| 400 |
+
switch (args.scalar_kind) {
|
| 401 |
+
case ScalarKind::Float32:
|
| 402 |
+
quantize_lookup_avx2<float>(args, scratch, output_offset, norm);
|
| 403 |
+
return;
|
| 404 |
+
case ScalarKind::Float16:
|
| 405 |
+
quantize_lookup_avx2<c10::Half>(args, scratch, output_offset, norm);
|
| 406 |
+
return;
|
| 407 |
+
case ScalarKind::BFloat16:
|
| 408 |
+
quantize_lookup_avx2<c10::BFloat16>(
|
| 409 |
+
args, scratch, output_offset, norm);
|
| 410 |
+
return;
|
| 411 |
+
}
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
void packed_adaln_msvc_avx2_range(
|
| 415 |
+
AdalnArgs const &args,
|
| 416 |
+
std::int64_t out_start,
|
| 417 |
+
std::int64_t out_end) {
|
| 418 |
+
if (args.group_size % 8 != 0 || args.padded_in_features % 2 != 0) {
|
| 419 |
+
packed_adaln_scalar_range(args, out_start, out_end);
|
| 420 |
+
return;
|
| 421 |
+
}
|
| 422 |
+
packed_adaln_tiled_range<
|
| 423 |
+
packed_adaln_avx2_rows<8>,
|
| 424 |
+
packed_adaln_avx2_rows<4>,
|
| 425 |
+
packed_adaln_avx2_rows<3>,
|
| 426 |
+
packed_adaln_avx2_rows<2>,
|
| 427 |
+
packed_adaln_avx2_rows<1>>(args, out_start, out_end);
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
} // namespace orbitquant::cpu
|
| 431 |
+
#endif
|
orbitquant_packed_matmul_cpu/cpu_pool.cpp
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "cpu_pool.h"
|
| 2 |
+
#include "cpu_threads.h"
|
| 3 |
+
|
| 4 |
+
#include <atomic>
|
| 5 |
+
#include <condition_variable>
|
| 6 |
+
#include <cstddef>
|
| 7 |
+
#include <mutex>
|
| 8 |
+
#include <thread>
|
| 9 |
+
|
| 10 |
+
namespace orbitquant::cpu {
|
| 11 |
+
namespace {
|
| 12 |
+
|
| 13 |
+
struct Job {
|
| 14 |
+
std::vector<std::pair<std::int64_t, std::int64_t>> const *ranges;
|
| 15 |
+
std::function<void(std::int64_t, std::int64_t)> const *fn;
|
| 16 |
+
std::atomic<std::size_t> next_range{0};
|
| 17 |
+
std::atomic<int> pending_workers{0};
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
class WorkerPool {
|
| 21 |
+
public:
|
| 22 |
+
static WorkerPool &instance() {
|
| 23 |
+
// Intentionally leaked: joining workers during static destruction can
|
| 24 |
+
// deadlock interpreter teardown, so the pool lives for the process.
|
| 25 |
+
static WorkerPool *pool = new WorkerPool(requested_threads() - 1);
|
| 26 |
+
return *pool;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
int worker_count() const {
|
| 30 |
+
return worker_count_;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
void run(Job &job) {
|
| 34 |
+
{
|
| 35 |
+
std::lock_guard<std::mutex> lock(mutex_);
|
| 36 |
+
job.pending_workers.store(worker_count_, std::memory_order_relaxed);
|
| 37 |
+
job_ = &job;
|
| 38 |
+
++generation_;
|
| 39 |
+
}
|
| 40 |
+
wake_cv_.notify_all();
|
| 41 |
+
drain(job);
|
| 42 |
+
std::unique_lock<std::mutex> lock(mutex_);
|
| 43 |
+
done_cv_.wait(lock, [&job] {
|
| 44 |
+
return job.pending_workers.load(std::memory_order_acquire) == 0;
|
| 45 |
+
});
|
| 46 |
+
job_ = nullptr;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
private:
|
| 50 |
+
explicit WorkerPool(int requested_workers)
|
| 51 |
+
: worker_count_(requested_workers > 0 ? requested_workers : 0) {
|
| 52 |
+
for (int worker = 0; worker < worker_count_; ++worker) {
|
| 53 |
+
std::thread([this] { worker_loop(); }).detach();
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
static void drain(Job &job) {
|
| 58 |
+
const std::size_t range_count = job.ranges->size();
|
| 59 |
+
for (;;) {
|
| 60 |
+
const std::size_t index =
|
| 61 |
+
job.next_range.fetch_add(1, std::memory_order_relaxed);
|
| 62 |
+
if (index >= range_count) {
|
| 63 |
+
return;
|
| 64 |
+
}
|
| 65 |
+
auto const &range = (*job.ranges)[index];
|
| 66 |
+
(*job.fn)(range.first, range.second);
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
void worker_loop() {
|
| 71 |
+
std::uint64_t seen_generation = 0;
|
| 72 |
+
for (;;) {
|
| 73 |
+
Job *job = nullptr;
|
| 74 |
+
{
|
| 75 |
+
std::unique_lock<std::mutex> lock(mutex_);
|
| 76 |
+
wake_cv_.wait(lock, [this, seen_generation] {
|
| 77 |
+
return generation_ != seen_generation;
|
| 78 |
+
});
|
| 79 |
+
seen_generation = generation_;
|
| 80 |
+
job = job_;
|
| 81 |
+
}
|
| 82 |
+
if (job == nullptr) {
|
| 83 |
+
continue;
|
| 84 |
+
}
|
| 85 |
+
drain(*job);
|
| 86 |
+
if (job->pending_workers.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
| 87 |
+
std::lock_guard<std::mutex> lock(mutex_);
|
| 88 |
+
done_cv_.notify_all();
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
const int worker_count_;
|
| 94 |
+
std::mutex mutex_;
|
| 95 |
+
std::condition_variable wake_cv_;
|
| 96 |
+
std::condition_variable done_cv_;
|
| 97 |
+
Job *job_ = nullptr;
|
| 98 |
+
std::uint64_t generation_ = 0;
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
+
} // namespace
|
| 102 |
+
|
| 103 |
+
void run_ranges(
|
| 104 |
+
std::vector<std::pair<std::int64_t, std::int64_t>> const &ranges,
|
| 105 |
+
std::function<void(std::int64_t, std::int64_t)> const &fn) {
|
| 106 |
+
if (ranges.empty()) {
|
| 107 |
+
return;
|
| 108 |
+
}
|
| 109 |
+
if (ranges.size() == 1) {
|
| 110 |
+
fn(ranges.front().first, ranges.front().second);
|
| 111 |
+
return;
|
| 112 |
+
}
|
| 113 |
+
WorkerPool &pool = WorkerPool::instance();
|
| 114 |
+
if (pool.worker_count() == 0) {
|
| 115 |
+
for (auto const &range : ranges) {
|
| 116 |
+
fn(range.first, range.second);
|
| 117 |
+
}
|
| 118 |
+
return;
|
| 119 |
+
}
|
| 120 |
+
Job job;
|
| 121 |
+
job.ranges = &ranges;
|
| 122 |
+
job.fn = &fn;
|
| 123 |
+
pool.run(job);
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
} // namespace orbitquant::cpu
|
orbitquant_packed_matmul_cpu/cpu_pool.h
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
#include <cstdint>
|
| 4 |
+
#include <functional>
|
| 5 |
+
#include <utility>
|
| 6 |
+
#include <vector>
|
| 7 |
+
|
| 8 |
+
namespace orbitquant::cpu {
|
| 9 |
+
|
| 10 |
+
// Executes fn over each [start, end) range using a lazily created persistent
|
| 11 |
+
// worker pool plus the calling thread. Ranges are disjoint, so results stay
|
| 12 |
+
// deterministic regardless of which thread runs which range. A single range
|
| 13 |
+
// (or an empty pool) runs inline on the caller.
|
| 14 |
+
void run_ranges(
|
| 15 |
+
std::vector<std::pair<std::int64_t, std::int64_t>> const &ranges,
|
| 16 |
+
std::function<void(std::int64_t, std::int64_t)> const &fn);
|
| 17 |
+
|
| 18 |
+
} // namespace orbitquant::cpu
|
orbitquant_packed_matmul_cpu/cpu_threads.cpp
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "cpu_threads.h"
|
| 2 |
+
|
| 3 |
+
#include <algorithm>
|
| 4 |
+
#include <cstdlib>
|
| 5 |
+
#include <fstream>
|
| 6 |
+
#include <set>
|
| 7 |
+
#include <string>
|
| 8 |
+
#include <thread>
|
| 9 |
+
#include <utility>
|
| 10 |
+
|
| 11 |
+
#if defined(__APPLE__)
|
| 12 |
+
#include <sys/sysctl.h>
|
| 13 |
+
#endif
|
| 14 |
+
|
| 15 |
+
#if defined(__linux__)
|
| 16 |
+
#include <sched.h>
|
| 17 |
+
#endif
|
| 18 |
+
|
| 19 |
+
namespace orbitquant::cpu {
|
| 20 |
+
namespace {
|
| 21 |
+
|
| 22 |
+
int environment_thread_count() {
|
| 23 |
+
char const *value = std::getenv("ORBITQUANT_CPU_THREADS");
|
| 24 |
+
if (value == nullptr) {
|
| 25 |
+
value = std::getenv("OMP_NUM_THREADS");
|
| 26 |
+
}
|
| 27 |
+
if (value == nullptr) {
|
| 28 |
+
return 0;
|
| 29 |
+
}
|
| 30 |
+
char *end = nullptr;
|
| 31 |
+
const long parsed = std::strtol(value, &end, 10);
|
| 32 |
+
if (end == value || parsed <= 0) {
|
| 33 |
+
return 0;
|
| 34 |
+
}
|
| 35 |
+
return static_cast<int>(std::min<long>(parsed, 64));
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
#if defined(__linux__)
|
| 39 |
+
int affinity_physical_core_count() {
|
| 40 |
+
cpu_set_t affinity;
|
| 41 |
+
CPU_ZERO(&affinity);
|
| 42 |
+
if (sched_getaffinity(0, sizeof(affinity), &affinity) != 0) {
|
| 43 |
+
return 0;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
int logical_cpus = 0;
|
| 47 |
+
std::set<std::pair<int, int>> physical_cores;
|
| 48 |
+
for (int cpu = 0; cpu < CPU_SETSIZE; ++cpu) {
|
| 49 |
+
if (!CPU_ISSET(cpu, &affinity)) {
|
| 50 |
+
continue;
|
| 51 |
+
}
|
| 52 |
+
++logical_cpus;
|
| 53 |
+
int package = -1;
|
| 54 |
+
int core = -1;
|
| 55 |
+
std::ifstream package_file(
|
| 56 |
+
"/sys/devices/system/cpu/cpu" + std::to_string(cpu) +
|
| 57 |
+
"/topology/physical_package_id");
|
| 58 |
+
std::ifstream core_file(
|
| 59 |
+
"/sys/devices/system/cpu/cpu" + std::to_string(cpu) +
|
| 60 |
+
"/topology/core_id");
|
| 61 |
+
if (package_file >> package && core_file >> core) {
|
| 62 |
+
physical_cores.emplace(package, core);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
return physical_cores.empty()
|
| 66 |
+
? logical_cpus
|
| 67 |
+
: static_cast<int>(physical_cores.size());
|
| 68 |
+
}
|
| 69 |
+
#endif
|
| 70 |
+
|
| 71 |
+
int default_thread_count() {
|
| 72 |
+
#if defined(__APPLE__)
|
| 73 |
+
int performance_cores = 0;
|
| 74 |
+
std::size_t size = sizeof(performance_cores);
|
| 75 |
+
if (sysctlbyname(
|
| 76 |
+
"hw.perflevel0.physicalcpu",
|
| 77 |
+
&performance_cores,
|
| 78 |
+
&size,
|
| 79 |
+
nullptr,
|
| 80 |
+
0) == 0 &&
|
| 81 |
+
performance_cores > 0) {
|
| 82 |
+
return std::min(performance_cores, 64);
|
| 83 |
+
}
|
| 84 |
+
#endif
|
| 85 |
+
#if defined(__linux__)
|
| 86 |
+
const int affinity_cores = affinity_physical_core_count();
|
| 87 |
+
if (affinity_cores > 0) {
|
| 88 |
+
return std::min(affinity_cores, 64);
|
| 89 |
+
}
|
| 90 |
+
#endif
|
| 91 |
+
const unsigned hardware = std::thread::hardware_concurrency();
|
| 92 |
+
return static_cast<int>(hardware == 0 ? 1 : std::min<unsigned>(hardware, 64));
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
} // namespace
|
| 96 |
+
|
| 97 |
+
int requested_threads() {
|
| 98 |
+
const int environment = environment_thread_count();
|
| 99 |
+
static const int default_threads = default_thread_count();
|
| 100 |
+
return environment > 0 ? environment : default_threads;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
} // namespace orbitquant::cpu
|
orbitquant_packed_matmul_cpu/cpu_threads.h
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
namespace orbitquant::cpu {
|
| 4 |
+
|
| 5 |
+
int requested_threads();
|
| 6 |
+
|
| 7 |
+
} // namespace orbitquant::cpu
|
orbitquant_packed_matmul_cpu/packed_adaln_cpu.cpp
ADDED
|
@@ -0,0 +1,878 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "cpu_pool.h"
|
| 2 |
+
#include "cpu_threads.h"
|
| 3 |
+
#include "cpu_kernel_args.h"
|
| 4 |
+
#include "packed_matmul_cpu.h"
|
| 5 |
+
#include "../torch-ext/torch_binding.h"
|
| 6 |
+
|
| 7 |
+
#include <torch/headeronly/core/DeviceType.h>
|
| 8 |
+
#include <torch/headeronly/core/ScalarType.h>
|
| 9 |
+
#include <torch/headeronly/macros/Macros.h>
|
| 10 |
+
#include <torch/headeronly/util/BFloat16.h>
|
| 11 |
+
|
| 12 |
+
#include <algorithm>
|
| 13 |
+
#include <cstdlib>
|
| 14 |
+
#include <cstdint>
|
| 15 |
+
#include <cstring>
|
| 16 |
+
#include <utility>
|
| 17 |
+
#include <vector>
|
| 18 |
+
|
| 19 |
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
| 20 |
+
#include <arm_neon.h>
|
| 21 |
+
#endif
|
| 22 |
+
|
| 23 |
+
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER)
|
| 24 |
+
#include <immintrin.h>
|
| 25 |
+
#define ORBITQUANT_TARGET_AVX2 __attribute__((target("avx2,fma,f16c")))
|
| 26 |
+
#define ORBITQUANT_TARGET_AVX512 \
|
| 27 |
+
__attribute__((target("avx512f,avx512dq,avx512bw,avx512vl,fma,f16c")))
|
| 28 |
+
#define ORBITQUANT_TARGET_AVX512_BF16 \
|
| 29 |
+
__attribute__((target( \
|
| 30 |
+
"avx512f,avx512dq,avx512bw,avx512vl,avx512bf16,fma,f16c")))
|
| 31 |
+
#define ORBITQUANT_HAS_AVX512_BF16_INTRINSICS 1
|
| 32 |
+
#define ORBITQUANT_NOINLINE __attribute__((noinline))
|
| 33 |
+
#endif
|
| 34 |
+
|
| 35 |
+
namespace {
|
| 36 |
+
|
| 37 |
+
using orbitquant::cpu::AdalnArgs;
|
| 38 |
+
using orbitquant::cpu::AdalnRangeFn;
|
| 39 |
+
|
| 40 |
+
inline float load_bfloat(
|
| 41 |
+
void const *data,
|
| 42 |
+
std::int64_t offset) {
|
| 43 |
+
return static_cast<float>(
|
| 44 |
+
static_cast<c10::BFloat16 const *>(data)[offset]);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
inline void store_bfloat(
|
| 48 |
+
void *data,
|
| 49 |
+
std::int64_t offset,
|
| 50 |
+
float value) {
|
| 51 |
+
static_cast<c10::BFloat16 *>(data)[offset] = c10::BFloat16(value);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
inline std::uint8_t unpack_adaln_index(
|
| 55 |
+
std::uint8_t const *packed,
|
| 56 |
+
std::int64_t flat_index) {
|
| 57 |
+
const std::uint8_t byte = packed[flat_index / 2];
|
| 58 |
+
return (flat_index & 1) == 0 ? byte & 15u : (byte >> 4) & 15u;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
inline float dequantized_adaln_value(
|
| 62 |
+
std::uint8_t index,
|
| 63 |
+
float scale) {
|
| 64 |
+
return static_cast<float>(
|
| 65 |
+
c10::BFloat16((static_cast<int>(index) - 8) * scale));
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
inline void fill_group_lut(float *lut, float scale) {
|
| 69 |
+
for (int index = 0; index < 16; ++index) {
|
| 70 |
+
lut[index] = dequantized_adaln_value(
|
| 71 |
+
static_cast<std::uint8_t>(index),
|
| 72 |
+
scale);
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
void packed_adaln_scalar_range(
|
| 77 |
+
AdalnArgs const &args,
|
| 78 |
+
std::int64_t out_start,
|
| 79 |
+
std::int64_t out_end) {
|
| 80 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 81 |
+
for (std::int64_t row = 0; row < args.rows; ++row) {
|
| 82 |
+
float accumulator = 0.0f;
|
| 83 |
+
const std::int64_t input_offset = row * args.in_features;
|
| 84 |
+
for (std::int64_t k = 0; k < args.in_features; ++k) {
|
| 85 |
+
const std::int64_t weight_offset =
|
| 86 |
+
out_col * args.padded_in_features + k;
|
| 87 |
+
const std::uint8_t index =
|
| 88 |
+
unpack_adaln_index(args.packed_weight, weight_offset);
|
| 89 |
+
const float scale =
|
| 90 |
+
args.scales[out_col * args.num_groups + k / args.group_size];
|
| 91 |
+
accumulator += load_bfloat(args.x, input_offset + k) *
|
| 92 |
+
dequantized_adaln_value(index, scale);
|
| 93 |
+
}
|
| 94 |
+
if (args.has_bias) {
|
| 95 |
+
accumulator += args.bias[out_col];
|
| 96 |
+
}
|
| 97 |
+
store_bfloat(
|
| 98 |
+
args.out,
|
| 99 |
+
row * args.out_features + out_col,
|
| 100 |
+
accumulator);
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
| 106 |
+
inline float32x4_t load_bfloat4(
|
| 107 |
+
void const *data,
|
| 108 |
+
std::int64_t offset) {
|
| 109 |
+
const uint16x4_t raw = vld1_u16(
|
| 110 |
+
static_cast<std::uint16_t const *>(data) + offset);
|
| 111 |
+
return vreinterpretq_f32_u32(vshlq_n_u32(vmovl_u16(raw), 16));
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
template <int row_tile>
|
| 115 |
+
inline void packed_adaln_neon_rows(
|
| 116 |
+
AdalnArgs const &args,
|
| 117 |
+
std::uint8_t const *packed_row,
|
| 118 |
+
float const *scale_row,
|
| 119 |
+
std::int64_t out_col,
|
| 120 |
+
std::int64_t row_start) {
|
| 121 |
+
float32x4_t accumulators[row_tile];
|
| 122 |
+
float tails[row_tile]{};
|
| 123 |
+
#pragma clang loop unroll(full)
|
| 124 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 125 |
+
accumulators[row] = vdupq_n_f32(0.0f);
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
for (std::int64_t group = 0; group < args.num_groups; ++group) {
|
| 129 |
+
float lut[16];
|
| 130 |
+
fill_group_lut(lut, scale_row[group]);
|
| 131 |
+
const std::int64_t group_start = group * args.group_size;
|
| 132 |
+
const std::int64_t group_end = std::min(
|
| 133 |
+
args.in_features,
|
| 134 |
+
group_start + args.group_size);
|
| 135 |
+
std::int64_t k = group_start;
|
| 136 |
+
for (; k + 4 <= group_end; k += 4) {
|
| 137 |
+
float weights[4];
|
| 138 |
+
#pragma clang loop unroll(full)
|
| 139 |
+
for (int lane = 0; lane < 4; ++lane) {
|
| 140 |
+
weights[lane] = lut[unpack_adaln_index(packed_row, k + lane)];
|
| 141 |
+
}
|
| 142 |
+
const float32x4_t weight = vld1q_f32(weights);
|
| 143 |
+
#pragma clang loop unroll(full)
|
| 144 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 145 |
+
const std::int64_t input_offset =
|
| 146 |
+
(row_start + row) * args.in_features + k;
|
| 147 |
+
accumulators[row] = vfmaq_f32(
|
| 148 |
+
accumulators[row],
|
| 149 |
+
load_bfloat4(args.x, input_offset),
|
| 150 |
+
weight);
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
for (; k < group_end; ++k) {
|
| 154 |
+
const float weight = lut[unpack_adaln_index(packed_row, k)];
|
| 155 |
+
#pragma clang loop unroll(full)
|
| 156 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 157 |
+
tails[row] += load_bfloat(
|
| 158 |
+
args.x,
|
| 159 |
+
(row_start + row) * args.in_features + k) * weight;
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
#pragma clang loop unroll(full)
|
| 165 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 166 |
+
float accumulator = vaddvq_f32(accumulators[row]) + tails[row];
|
| 167 |
+
if (args.has_bias) {
|
| 168 |
+
accumulator += args.bias[out_col];
|
| 169 |
+
}
|
| 170 |
+
store_bfloat(
|
| 171 |
+
args.out,
|
| 172 |
+
(row_start + row) * args.out_features + out_col,
|
| 173 |
+
accumulator);
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
void packed_adaln_neon_range(
|
| 178 |
+
AdalnArgs const &args,
|
| 179 |
+
std::int64_t out_start,
|
| 180 |
+
std::int64_t out_end) {
|
| 181 |
+
if (args.group_size % 4 != 0 || args.padded_in_features % 2 != 0) {
|
| 182 |
+
packed_adaln_scalar_range(args, out_start, out_end);
|
| 183 |
+
return;
|
| 184 |
+
}
|
| 185 |
+
constexpr int kPrimaryRowTile = 8;
|
| 186 |
+
const std::int64_t packed_row_bytes = args.padded_in_features / 2;
|
| 187 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 188 |
+
const auto *packed_row =
|
| 189 |
+
args.packed_weight + out_col * packed_row_bytes;
|
| 190 |
+
const auto *scale_row =
|
| 191 |
+
args.scales + out_col * args.num_groups;
|
| 192 |
+
std::int64_t row = 0;
|
| 193 |
+
for (; row + kPrimaryRowTile <= args.rows; row += kPrimaryRowTile) {
|
| 194 |
+
packed_adaln_neon_rows<kPrimaryRowTile>(
|
| 195 |
+
args, packed_row, scale_row, out_col, row);
|
| 196 |
+
}
|
| 197 |
+
if (row + 4 <= args.rows) {
|
| 198 |
+
packed_adaln_neon_rows<4>(
|
| 199 |
+
args, packed_row, scale_row, out_col, row);
|
| 200 |
+
row += 4;
|
| 201 |
+
}
|
| 202 |
+
switch (args.rows - row) {
|
| 203 |
+
case 3:
|
| 204 |
+
packed_adaln_neon_rows<3>(
|
| 205 |
+
args, packed_row, scale_row, out_col, row);
|
| 206 |
+
break;
|
| 207 |
+
case 2:
|
| 208 |
+
packed_adaln_neon_rows<2>(
|
| 209 |
+
args, packed_row, scale_row, out_col, row);
|
| 210 |
+
break;
|
| 211 |
+
case 1:
|
| 212 |
+
packed_adaln_neon_rows<1>(
|
| 213 |
+
args, packed_row, scale_row, out_col, row);
|
| 214 |
+
break;
|
| 215 |
+
default:
|
| 216 |
+
break;
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
#else
|
| 221 |
+
void packed_adaln_neon_range(
|
| 222 |
+
AdalnArgs const &args,
|
| 223 |
+
std::int64_t out_start,
|
| 224 |
+
std::int64_t out_end) {
|
| 225 |
+
packed_adaln_scalar_range(args, out_start, out_end);
|
| 226 |
+
}
|
| 227 |
+
#endif
|
| 228 |
+
|
| 229 |
+
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER)
|
| 230 |
+
ORBITQUANT_TARGET_AVX2 inline __m256 load_bfloat8(
|
| 231 |
+
void const *data,
|
| 232 |
+
std::int64_t offset) {
|
| 233 |
+
const auto *source =
|
| 234 |
+
static_cast<std::uint16_t const *>(data) + offset;
|
| 235 |
+
const __m128i packed =
|
| 236 |
+
_mm_loadu_si128(reinterpret_cast<__m128i const *>(source));
|
| 237 |
+
return _mm256_castsi256_ps(
|
| 238 |
+
_mm256_slli_epi32(_mm256_cvtepu16_epi32(packed), 16));
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
ORBITQUANT_TARGET_AVX512 inline __m512 load_bfloat16(
|
| 242 |
+
void const *data,
|
| 243 |
+
std::int64_t offset) {
|
| 244 |
+
const auto *source =
|
| 245 |
+
static_cast<std::uint16_t const *>(data) + offset;
|
| 246 |
+
const __m256i packed =
|
| 247 |
+
_mm256_loadu_si256(reinterpret_cast<__m256i const *>(source));
|
| 248 |
+
return _mm512_castsi512_ps(
|
| 249 |
+
_mm512_slli_epi32(_mm512_cvtepu16_epi32(packed), 16));
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
ORBITQUANT_TARGET_AVX2 inline float horizontal_sum(__m256 value) {
|
| 253 |
+
const __m128 halves = _mm_add_ps(
|
| 254 |
+
_mm256_castps256_ps128(value),
|
| 255 |
+
_mm256_extractf128_ps(value, 1));
|
| 256 |
+
const __m128 pairs = _mm_hadd_ps(halves, halves);
|
| 257 |
+
return _mm_cvtss_f32(_mm_hadd_ps(pairs, pairs));
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
ORBITQUANT_TARGET_AVX512 inline float horizontal_sum(__m512 value) {
|
| 261 |
+
return _mm512_reduce_add_ps(value);
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
template <int row_tile>
|
| 265 |
+
ORBITQUANT_TARGET_AVX2 inline void packed_adaln_avx2_rows(
|
| 266 |
+
AdalnArgs const &args,
|
| 267 |
+
std::uint8_t const *packed_row,
|
| 268 |
+
float const *scale_row,
|
| 269 |
+
std::int64_t out_col,
|
| 270 |
+
std::int64_t row_start) {
|
| 271 |
+
__m256 accumulators[row_tile];
|
| 272 |
+
float tails[row_tile]{};
|
| 273 |
+
#pragma clang loop unroll(full)
|
| 274 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 275 |
+
accumulators[row] = _mm256_setzero_ps();
|
| 276 |
+
}
|
| 277 |
+
const __m128i nibble_mask = _mm_set1_epi8(15);
|
| 278 |
+
const __m256i low_table_limit = _mm256_set1_epi32(7);
|
| 279 |
+
|
| 280 |
+
for (std::int64_t group = 0; group < args.num_groups; ++group) {
|
| 281 |
+
float lut[16];
|
| 282 |
+
fill_group_lut(lut, scale_row[group]);
|
| 283 |
+
const __m256 lut_low = _mm256_loadu_ps(lut);
|
| 284 |
+
const __m256 lut_high = _mm256_loadu_ps(lut + 8);
|
| 285 |
+
const std::int64_t group_start = group * args.group_size;
|
| 286 |
+
const std::int64_t group_end = std::min(
|
| 287 |
+
args.in_features,
|
| 288 |
+
group_start + args.group_size);
|
| 289 |
+
std::int64_t k = group_start;
|
| 290 |
+
for (; k + 8 <= group_end; k += 8) {
|
| 291 |
+
std::int32_t packed;
|
| 292 |
+
std::memcpy(&packed, packed_row + k / 2, sizeof(packed));
|
| 293 |
+
const __m128i bytes = _mm_cvtsi32_si128(packed);
|
| 294 |
+
const __m128i low = _mm_and_si128(bytes, nibble_mask);
|
| 295 |
+
const __m128i high = _mm_and_si128(
|
| 296 |
+
_mm_srli_epi16(bytes, 4),
|
| 297 |
+
nibble_mask);
|
| 298 |
+
const __m256i indices =
|
| 299 |
+
_mm256_cvtepu8_epi32(_mm_unpacklo_epi8(low, high));
|
| 300 |
+
const __m256 weight = _mm256_blendv_ps(
|
| 301 |
+
_mm256_permutevar8x32_ps(lut_low, indices),
|
| 302 |
+
_mm256_permutevar8x32_ps(lut_high, indices),
|
| 303 |
+
_mm256_castsi256_ps(
|
| 304 |
+
_mm256_cmpgt_epi32(indices, low_table_limit)));
|
| 305 |
+
#pragma clang loop unroll(full)
|
| 306 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 307 |
+
const std::int64_t input_offset =
|
| 308 |
+
(row_start + row) * args.in_features + k;
|
| 309 |
+
accumulators[row] = _mm256_fmadd_ps(
|
| 310 |
+
load_bfloat8(args.x, input_offset),
|
| 311 |
+
weight,
|
| 312 |
+
accumulators[row]);
|
| 313 |
+
}
|
| 314 |
+
}
|
| 315 |
+
for (; k < group_end; ++k) {
|
| 316 |
+
const float weight = lut[unpack_adaln_index(packed_row, k)];
|
| 317 |
+
#pragma clang loop unroll(full)
|
| 318 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 319 |
+
tails[row] += load_bfloat(
|
| 320 |
+
args.x,
|
| 321 |
+
(row_start + row) * args.in_features + k) * weight;
|
| 322 |
+
}
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
#pragma clang loop unroll(full)
|
| 327 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 328 |
+
float accumulator = horizontal_sum(accumulators[row]) + tails[row];
|
| 329 |
+
if (args.has_bias) {
|
| 330 |
+
accumulator += args.bias[out_col];
|
| 331 |
+
}
|
| 332 |
+
store_bfloat(
|
| 333 |
+
args.out,
|
| 334 |
+
(row_start + row) * args.out_features + out_col,
|
| 335 |
+
accumulator);
|
| 336 |
+
}
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
template <int row_tile>
|
| 340 |
+
ORBITQUANT_TARGET_AVX512 inline void packed_adaln_avx512_rows(
|
| 341 |
+
AdalnArgs const &args,
|
| 342 |
+
std::uint8_t const *packed_row,
|
| 343 |
+
float const *scale_row,
|
| 344 |
+
std::int64_t out_col,
|
| 345 |
+
std::int64_t row_start) {
|
| 346 |
+
__m512 accumulators[row_tile];
|
| 347 |
+
float tails[row_tile]{};
|
| 348 |
+
#pragma clang loop unroll(full)
|
| 349 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 350 |
+
accumulators[row] = _mm512_setzero_ps();
|
| 351 |
+
}
|
| 352 |
+
const __m128i nibble_mask = _mm_set1_epi8(15);
|
| 353 |
+
|
| 354 |
+
for (std::int64_t group = 0; group < args.num_groups; ++group) {
|
| 355 |
+
float lut[16];
|
| 356 |
+
fill_group_lut(lut, scale_row[group]);
|
| 357 |
+
const __m512 centroid_lut = _mm512_loadu_ps(lut);
|
| 358 |
+
const std::int64_t group_start = group * args.group_size;
|
| 359 |
+
const std::int64_t group_end = std::min(
|
| 360 |
+
args.in_features,
|
| 361 |
+
group_start + args.group_size);
|
| 362 |
+
std::int64_t k = group_start;
|
| 363 |
+
for (; k + 16 <= group_end; k += 16) {
|
| 364 |
+
std::int64_t packed;
|
| 365 |
+
std::memcpy(&packed, packed_row + k / 2, sizeof(packed));
|
| 366 |
+
const __m128i bytes = _mm_cvtsi64_si128(packed);
|
| 367 |
+
const __m128i low = _mm_and_si128(bytes, nibble_mask);
|
| 368 |
+
const __m128i high = _mm_and_si128(
|
| 369 |
+
_mm_srli_epi16(bytes, 4),
|
| 370 |
+
nibble_mask);
|
| 371 |
+
const __m512i indices =
|
| 372 |
+
_mm512_cvtepu8_epi32(_mm_unpacklo_epi8(low, high));
|
| 373 |
+
const __m512 weight =
|
| 374 |
+
_mm512_permutexvar_ps(indices, centroid_lut);
|
| 375 |
+
#pragma clang loop unroll(full)
|
| 376 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 377 |
+
const std::int64_t input_offset =
|
| 378 |
+
(row_start + row) * args.in_features + k;
|
| 379 |
+
accumulators[row] = _mm512_fmadd_ps(
|
| 380 |
+
load_bfloat16(args.x, input_offset),
|
| 381 |
+
weight,
|
| 382 |
+
accumulators[row]);
|
| 383 |
+
}
|
| 384 |
+
}
|
| 385 |
+
for (; k < group_end; ++k) {
|
| 386 |
+
const float weight = lut[unpack_adaln_index(packed_row, k)];
|
| 387 |
+
#pragma clang loop unroll(full)
|
| 388 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 389 |
+
tails[row] += load_bfloat(
|
| 390 |
+
args.x,
|
| 391 |
+
(row_start + row) * args.in_features + k) * weight;
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
#pragma clang loop unroll(full)
|
| 397 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 398 |
+
float accumulator = horizontal_sum(accumulators[row]) + tails[row];
|
| 399 |
+
if (args.has_bias) {
|
| 400 |
+
accumulator += args.bias[out_col];
|
| 401 |
+
}
|
| 402 |
+
store_bfloat(
|
| 403 |
+
args.out,
|
| 404 |
+
(row_start + row) * args.out_features + out_col,
|
| 405 |
+
accumulator);
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
#if defined(ORBITQUANT_HAS_AVX512_BF16_INTRINSICS)
|
| 410 |
+
template <bool aligned_k>
|
| 411 |
+
ORBITQUANT_TARGET_AVX512_BF16 inline __m512bh load_bfloat32(
|
| 412 |
+
std::uint16_t const *data,
|
| 413 |
+
__mmask32 valid_mask) {
|
| 414 |
+
if constexpr (aligned_k) {
|
| 415 |
+
return (__m512bh)_mm512_loadu_si512(data);
|
| 416 |
+
}
|
| 417 |
+
return (__m512bh)_mm512_maskz_loadu_epi16(valid_mask, data);
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
template <int row_tile, bool aligned_k>
|
| 421 |
+
ORBITQUANT_TARGET_AVX512_BF16 inline void packed_adaln_avx512_bf16_rows(
|
| 422 |
+
AdalnArgs const &args,
|
| 423 |
+
std::uint8_t const *packed_row,
|
| 424 |
+
float const *scale_row,
|
| 425 |
+
std::int64_t out_col,
|
| 426 |
+
std::int64_t row_start) {
|
| 427 |
+
static_assert(row_tile >= 1 && row_tile <= 8);
|
| 428 |
+
__m512 accumulator0 = _mm512_setzero_ps();
|
| 429 |
+
__m512 accumulator1 = _mm512_setzero_ps();
|
| 430 |
+
__m512 accumulator2 = _mm512_setzero_ps();
|
| 431 |
+
__m512 accumulator3 = _mm512_setzero_ps();
|
| 432 |
+
__m512 accumulator4 = _mm512_setzero_ps();
|
| 433 |
+
__m512 accumulator5 = _mm512_setzero_ps();
|
| 434 |
+
__m512 accumulator6 = _mm512_setzero_ps();
|
| 435 |
+
__m512 accumulator7 = _mm512_setzero_ps();
|
| 436 |
+
const __m128i nibble_mask = _mm_set1_epi8(15);
|
| 437 |
+
const __m512 signed_codes = _mm512_setr_ps(
|
| 438 |
+
-8.0f, -7.0f, -6.0f, -5.0f,
|
| 439 |
+
-4.0f, -3.0f, -2.0f, -1.0f,
|
| 440 |
+
0.0f, 1.0f, 2.0f, 3.0f,
|
| 441 |
+
4.0f, 5.0f, 6.0f, 7.0f);
|
| 442 |
+
const auto *input_base =
|
| 443 |
+
static_cast<std::uint16_t const *>(args.x) +
|
| 444 |
+
row_start * args.in_features;
|
| 445 |
+
const std::int64_t input_stride = args.in_features;
|
| 446 |
+
|
| 447 |
+
for (std::int64_t group = 0; group < args.num_groups; ++group) {
|
| 448 |
+
const __m512 scaled_codes = _mm512_mul_ps(
|
| 449 |
+
signed_codes,
|
| 450 |
+
_mm512_set1_ps(scale_row[group]));
|
| 451 |
+
const __m512i lut_words = (__m512i)_mm512_cvtne2ps_pbh(
|
| 452 |
+
scaled_codes,
|
| 453 |
+
scaled_codes);
|
| 454 |
+
const std::int64_t group_start = group * args.group_size;
|
| 455 |
+
for (std::int64_t offset = 0; offset < args.group_size; offset += 32) {
|
| 456 |
+
const std::int64_t k = group_start + offset;
|
| 457 |
+
__mmask32 valid_mask = static_cast<__mmask32>(0xffffffffu);
|
| 458 |
+
if constexpr (!aligned_k) {
|
| 459 |
+
const std::int64_t valid = std::max<std::int64_t>(
|
| 460 |
+
0,
|
| 461 |
+
std::min<std::int64_t>(32, args.in_features - k));
|
| 462 |
+
if (valid == 0) {
|
| 463 |
+
continue;
|
| 464 |
+
}
|
| 465 |
+
valid_mask = valid == 32
|
| 466 |
+
? static_cast<__mmask32>(0xffffffffu)
|
| 467 |
+
: static_cast<__mmask32>((1u << valid) - 1u);
|
| 468 |
+
}
|
| 469 |
+
const __m128i bytes = _mm_loadu_si128(
|
| 470 |
+
reinterpret_cast<__m128i const *>(packed_row + k / 2));
|
| 471 |
+
const __m128i low = _mm_and_si128(bytes, nibble_mask);
|
| 472 |
+
const __m128i high = _mm_and_si128(
|
| 473 |
+
_mm_srli_epi16(bytes, 4),
|
| 474 |
+
nibble_mask);
|
| 475 |
+
const __m256i packed_indices = _mm256_set_m128i(
|
| 476 |
+
_mm_unpackhi_epi8(low, high),
|
| 477 |
+
_mm_unpacklo_epi8(low, high));
|
| 478 |
+
const __m512i indices =
|
| 479 |
+
_mm512_cvtepu8_epi16(packed_indices);
|
| 480 |
+
const __m512bh weights = (__m512bh)_mm512_permutexvar_epi16(
|
| 481 |
+
indices,
|
| 482 |
+
lut_words);
|
| 483 |
+
accumulator0 = _mm512_dpbf16_ps(
|
| 484 |
+
accumulator0,
|
| 485 |
+
load_bfloat32<aligned_k>(input_base + k, valid_mask),
|
| 486 |
+
weights);
|
| 487 |
+
if constexpr (row_tile > 1) {
|
| 488 |
+
accumulator1 = _mm512_dpbf16_ps(
|
| 489 |
+
accumulator1,
|
| 490 |
+
load_bfloat32<aligned_k>(
|
| 491 |
+
input_base + input_stride + k,
|
| 492 |
+
valid_mask),
|
| 493 |
+
weights);
|
| 494 |
+
}
|
| 495 |
+
if constexpr (row_tile > 2) {
|
| 496 |
+
accumulator2 = _mm512_dpbf16_ps(
|
| 497 |
+
accumulator2,
|
| 498 |
+
load_bfloat32<aligned_k>(
|
| 499 |
+
input_base + 2 * input_stride + k,
|
| 500 |
+
valid_mask),
|
| 501 |
+
weights);
|
| 502 |
+
}
|
| 503 |
+
if constexpr (row_tile > 3) {
|
| 504 |
+
accumulator3 = _mm512_dpbf16_ps(
|
| 505 |
+
accumulator3,
|
| 506 |
+
load_bfloat32<aligned_k>(
|
| 507 |
+
input_base + 3 * input_stride + k,
|
| 508 |
+
valid_mask),
|
| 509 |
+
weights);
|
| 510 |
+
}
|
| 511 |
+
if constexpr (row_tile > 4) {
|
| 512 |
+
accumulator4 = _mm512_dpbf16_ps(
|
| 513 |
+
accumulator4,
|
| 514 |
+
load_bfloat32<aligned_k>(
|
| 515 |
+
input_base + 4 * input_stride + k,
|
| 516 |
+
valid_mask),
|
| 517 |
+
weights);
|
| 518 |
+
}
|
| 519 |
+
if constexpr (row_tile > 5) {
|
| 520 |
+
accumulator5 = _mm512_dpbf16_ps(
|
| 521 |
+
accumulator5,
|
| 522 |
+
load_bfloat32<aligned_k>(
|
| 523 |
+
input_base + 5 * input_stride + k,
|
| 524 |
+
valid_mask),
|
| 525 |
+
weights);
|
| 526 |
+
}
|
| 527 |
+
if constexpr (row_tile > 6) {
|
| 528 |
+
accumulator6 = _mm512_dpbf16_ps(
|
| 529 |
+
accumulator6,
|
| 530 |
+
load_bfloat32<aligned_k>(
|
| 531 |
+
input_base + 6 * input_stride + k,
|
| 532 |
+
valid_mask),
|
| 533 |
+
weights);
|
| 534 |
+
}
|
| 535 |
+
if constexpr (row_tile > 7) {
|
| 536 |
+
accumulator7 = _mm512_dpbf16_ps(
|
| 537 |
+
accumulator7,
|
| 538 |
+
load_bfloat32<aligned_k>(
|
| 539 |
+
input_base + 7 * input_stride + k,
|
| 540 |
+
valid_mask),
|
| 541 |
+
weights);
|
| 542 |
+
}
|
| 543 |
+
}
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
const float bias = args.has_bias ? args.bias[out_col] : 0.0f;
|
| 547 |
+
const std::int64_t output_offset =
|
| 548 |
+
row_start * args.out_features + out_col;
|
| 549 |
+
store_bfloat(
|
| 550 |
+
args.out,
|
| 551 |
+
output_offset,
|
| 552 |
+
horizontal_sum(accumulator0) + bias);
|
| 553 |
+
if constexpr (row_tile > 1) {
|
| 554 |
+
store_bfloat(
|
| 555 |
+
args.out,
|
| 556 |
+
output_offset + args.out_features,
|
| 557 |
+
horizontal_sum(accumulator1) + bias);
|
| 558 |
+
}
|
| 559 |
+
if constexpr (row_tile > 2) {
|
| 560 |
+
store_bfloat(
|
| 561 |
+
args.out,
|
| 562 |
+
output_offset + 2 * args.out_features,
|
| 563 |
+
horizontal_sum(accumulator2) + bias);
|
| 564 |
+
}
|
| 565 |
+
if constexpr (row_tile > 3) {
|
| 566 |
+
store_bfloat(
|
| 567 |
+
args.out,
|
| 568 |
+
output_offset + 3 * args.out_features,
|
| 569 |
+
horizontal_sum(accumulator3) + bias);
|
| 570 |
+
}
|
| 571 |
+
if constexpr (row_tile > 4) {
|
| 572 |
+
store_bfloat(
|
| 573 |
+
args.out,
|
| 574 |
+
output_offset + 4 * args.out_features,
|
| 575 |
+
horizontal_sum(accumulator4) + bias);
|
| 576 |
+
}
|
| 577 |
+
if constexpr (row_tile > 5) {
|
| 578 |
+
store_bfloat(
|
| 579 |
+
args.out,
|
| 580 |
+
output_offset + 5 * args.out_features,
|
| 581 |
+
horizontal_sum(accumulator5) + bias);
|
| 582 |
+
}
|
| 583 |
+
if constexpr (row_tile > 6) {
|
| 584 |
+
store_bfloat(
|
| 585 |
+
args.out,
|
| 586 |
+
output_offset + 6 * args.out_features,
|
| 587 |
+
horizontal_sum(accumulator6) + bias);
|
| 588 |
+
}
|
| 589 |
+
if constexpr (row_tile > 7) {
|
| 590 |
+
store_bfloat(
|
| 591 |
+
args.out,
|
| 592 |
+
output_offset + 7 * args.out_features,
|
| 593 |
+
horizontal_sum(accumulator7) + bias);
|
| 594 |
+
}
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
bool adaln_avx512_bf16_available() {
|
| 598 |
+
__builtin_cpu_init();
|
| 599 |
+
return orbitquant::cpu::packed_matmul_x86_avx512_available() &&
|
| 600 |
+
__builtin_cpu_supports("avx512bf16");
|
| 601 |
+
}
|
| 602 |
+
#else
|
| 603 |
+
bool adaln_avx512_bf16_available() {
|
| 604 |
+
return false;
|
| 605 |
+
}
|
| 606 |
+
#endif
|
| 607 |
+
|
| 608 |
+
template <
|
| 609 |
+
void (*rows8)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t),
|
| 610 |
+
void (*rows4)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t),
|
| 611 |
+
void (*rows3)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t),
|
| 612 |
+
void (*rows2)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t),
|
| 613 |
+
void (*rows1)(AdalnArgs const &, std::uint8_t const *, float const *, std::int64_t, std::int64_t)>
|
| 614 |
+
void packed_adaln_x86_tiled_range(
|
| 615 |
+
AdalnArgs const &args,
|
| 616 |
+
std::int64_t out_start,
|
| 617 |
+
std::int64_t out_end) {
|
| 618 |
+
const std::int64_t packed_row_bytes = args.padded_in_features / 2;
|
| 619 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 620 |
+
const auto *packed_row =
|
| 621 |
+
args.packed_weight + out_col * packed_row_bytes;
|
| 622 |
+
const auto *scale_row =
|
| 623 |
+
args.scales + out_col * args.num_groups;
|
| 624 |
+
std::int64_t row = 0;
|
| 625 |
+
for (; row + 8 <= args.rows; row += 8) {
|
| 626 |
+
rows8(args, packed_row, scale_row, out_col, row);
|
| 627 |
+
}
|
| 628 |
+
if (row + 4 <= args.rows) {
|
| 629 |
+
rows4(args, packed_row, scale_row, out_col, row);
|
| 630 |
+
row += 4;
|
| 631 |
+
}
|
| 632 |
+
switch (args.rows - row) {
|
| 633 |
+
case 3:
|
| 634 |
+
rows3(args, packed_row, scale_row, out_col, row);
|
| 635 |
+
break;
|
| 636 |
+
case 2:
|
| 637 |
+
rows2(args, packed_row, scale_row, out_col, row);
|
| 638 |
+
break;
|
| 639 |
+
case 1:
|
| 640 |
+
rows1(args, packed_row, scale_row, out_col, row);
|
| 641 |
+
break;
|
| 642 |
+
default:
|
| 643 |
+
break;
|
| 644 |
+
}
|
| 645 |
+
}
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
void packed_adaln_avx2_range(
|
| 649 |
+
AdalnArgs const &args,
|
| 650 |
+
std::int64_t out_start,
|
| 651 |
+
std::int64_t out_end) {
|
| 652 |
+
if (args.group_size % 8 != 0 || args.padded_in_features % 2 != 0) {
|
| 653 |
+
packed_adaln_scalar_range(args, out_start, out_end);
|
| 654 |
+
return;
|
| 655 |
+
}
|
| 656 |
+
packed_adaln_x86_tiled_range<
|
| 657 |
+
packed_adaln_avx2_rows<8>,
|
| 658 |
+
packed_adaln_avx2_rows<4>,
|
| 659 |
+
packed_adaln_avx2_rows<3>,
|
| 660 |
+
packed_adaln_avx2_rows<2>,
|
| 661 |
+
packed_adaln_avx2_rows<1>>(args, out_start, out_end);
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
void packed_adaln_avx512_range(
|
| 665 |
+
AdalnArgs const &args,
|
| 666 |
+
std::int64_t out_start,
|
| 667 |
+
std::int64_t out_end) {
|
| 668 |
+
#if defined(ORBITQUANT_HAS_AVX512_BF16_INTRINSICS)
|
| 669 |
+
if (args.group_size % 32 == 0 &&
|
| 670 |
+
args.padded_in_features % 2 == 0 &&
|
| 671 |
+
adaln_avx512_bf16_available()) {
|
| 672 |
+
if (args.in_features % args.group_size == 0) {
|
| 673 |
+
packed_adaln_x86_tiled_range<
|
| 674 |
+
packed_adaln_avx512_bf16_rows<8, true>,
|
| 675 |
+
packed_adaln_avx512_bf16_rows<4, true>,
|
| 676 |
+
packed_adaln_avx512_bf16_rows<3, true>,
|
| 677 |
+
packed_adaln_avx512_bf16_rows<2, true>,
|
| 678 |
+
packed_adaln_avx512_bf16_rows<1, true>>(args, out_start, out_end);
|
| 679 |
+
return;
|
| 680 |
+
}
|
| 681 |
+
packed_adaln_x86_tiled_range<
|
| 682 |
+
packed_adaln_avx512_bf16_rows<8, false>,
|
| 683 |
+
packed_adaln_avx512_bf16_rows<4, false>,
|
| 684 |
+
packed_adaln_avx512_bf16_rows<3, false>,
|
| 685 |
+
packed_adaln_avx512_bf16_rows<2, false>,
|
| 686 |
+
packed_adaln_avx512_bf16_rows<1, false>>(args, out_start, out_end);
|
| 687 |
+
return;
|
| 688 |
+
}
|
| 689 |
+
#endif
|
| 690 |
+
if (args.group_size % 16 != 0 || args.padded_in_features % 2 != 0) {
|
| 691 |
+
packed_adaln_scalar_range(args, out_start, out_end);
|
| 692 |
+
return;
|
| 693 |
+
}
|
| 694 |
+
packed_adaln_x86_tiled_range<
|
| 695 |
+
packed_adaln_avx512_rows<8>,
|
| 696 |
+
packed_adaln_avx512_rows<4>,
|
| 697 |
+
packed_adaln_avx512_rows<3>,
|
| 698 |
+
packed_adaln_avx512_rows<2>,
|
| 699 |
+
packed_adaln_avx512_rows<1>>(args, out_start, out_end);
|
| 700 |
+
}
|
| 701 |
+
#else
|
| 702 |
+
void packed_adaln_avx2_range(
|
| 703 |
+
AdalnArgs const &args,
|
| 704 |
+
std::int64_t out_start,
|
| 705 |
+
std::int64_t out_end) {
|
| 706 |
+
packed_adaln_scalar_range(args, out_start, out_end);
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
void packed_adaln_avx512_range(
|
| 710 |
+
AdalnArgs const &args,
|
| 711 |
+
std::int64_t out_start,
|
| 712 |
+
std::int64_t out_end) {
|
| 713 |
+
packed_adaln_scalar_range(args, out_start, out_end);
|
| 714 |
+
}
|
| 715 |
+
#endif
|
| 716 |
+
|
| 717 |
+
AdalnRangeFn select_adaln_kernel() {
|
| 718 |
+
const char *requested = std::getenv("ORBITQUANT_CPU_ISA");
|
| 719 |
+
if (requested == nullptr || std::strcmp(requested, "auto") == 0) {
|
| 720 |
+
if (orbitquant::cpu::packed_matmul_x86_avx512_available()) {
|
| 721 |
+
return packed_adaln_avx512_range;
|
| 722 |
+
}
|
| 723 |
+
if (orbitquant::cpu::packed_matmul_x86_avx2_available()) {
|
| 724 |
+
#if defined(_MSC_VER) && defined(_M_X64)
|
| 725 |
+
return orbitquant::cpu::packed_adaln_msvc_avx2_range;
|
| 726 |
+
#else
|
| 727 |
+
return packed_adaln_avx2_range;
|
| 728 |
+
#endif
|
| 729 |
+
}
|
| 730 |
+
if (orbitquant::cpu::packed_matmul_neon_available()) {
|
| 731 |
+
return packed_adaln_neon_range;
|
| 732 |
+
}
|
| 733 |
+
return packed_adaln_scalar_range;
|
| 734 |
+
}
|
| 735 |
+
if (std::strcmp(requested, "scalar") == 0) {
|
| 736 |
+
return packed_adaln_scalar_range;
|
| 737 |
+
}
|
| 738 |
+
if (std::strcmp(requested, "avx2") == 0) {
|
| 739 |
+
STD_TORCH_CHECK(
|
| 740 |
+
orbitquant::cpu::packed_matmul_x86_avx2_available(),
|
| 741 |
+
"ORBITQUANT_CPU_ISA=avx2 requested AVX2/FMA/F16C on an unsupported CPU");
|
| 742 |
+
#if defined(_MSC_VER) && defined(_M_X64)
|
| 743 |
+
return orbitquant::cpu::packed_adaln_msvc_avx2_range;
|
| 744 |
+
#else
|
| 745 |
+
return packed_adaln_avx2_range;
|
| 746 |
+
#endif
|
| 747 |
+
}
|
| 748 |
+
if (std::strcmp(requested, "avx512") == 0) {
|
| 749 |
+
STD_TORCH_CHECK(
|
| 750 |
+
orbitquant::cpu::packed_matmul_x86_avx512_available(),
|
| 751 |
+
"ORBITQUANT_CPU_ISA=avx512 requested AVX-512F/DQ/BW/VL on an unsupported CPU");
|
| 752 |
+
return packed_adaln_avx512_range;
|
| 753 |
+
}
|
| 754 |
+
if (std::strcmp(requested, "neon") == 0) {
|
| 755 |
+
STD_TORCH_CHECK(
|
| 756 |
+
orbitquant::cpu::packed_matmul_neon_available(),
|
| 757 |
+
"ORBITQUANT_CPU_ISA=neon requested NEON on an unsupported CPU");
|
| 758 |
+
return packed_adaln_neon_range;
|
| 759 |
+
}
|
| 760 |
+
STD_TORCH_CHECK(
|
| 761 |
+
false,
|
| 762 |
+
"ORBITQUANT_CPU_ISA must be auto, scalar, avx2, avx512, or neon");
|
| 763 |
+
return packed_adaln_scalar_range;
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
void parallel_packed_adaln(
|
| 767 |
+
AdalnArgs const &args,
|
| 768 |
+
AdalnRangeFn function) {
|
| 769 |
+
const std::int64_t arithmetic =
|
| 770 |
+
args.rows * args.out_features * args.in_features;
|
| 771 |
+
const int max_threads = orbitquant::cpu::requested_threads();
|
| 772 |
+
const int threads = arithmetic < 1'000'000
|
| 773 |
+
? 1
|
| 774 |
+
: std::max<int>(
|
| 775 |
+
1,
|
| 776 |
+
std::min<std::int64_t>(
|
| 777 |
+
max_threads,
|
| 778 |
+
args.out_features / 16));
|
| 779 |
+
if (threads == 1) {
|
| 780 |
+
function(args, 0, args.out_features);
|
| 781 |
+
return;
|
| 782 |
+
}
|
| 783 |
+
|
| 784 |
+
std::vector<std::pair<std::int64_t, std::int64_t>> ranges;
|
| 785 |
+
ranges.reserve(threads);
|
| 786 |
+
const std::int64_t columns_per_thread =
|
| 787 |
+
(args.out_features + threads - 1) / threads;
|
| 788 |
+
for (int thread = 0; thread < threads; ++thread) {
|
| 789 |
+
const std::int64_t start = thread * columns_per_thread;
|
| 790 |
+
const std::int64_t end = std::min(
|
| 791 |
+
args.out_features,
|
| 792 |
+
start + columns_per_thread);
|
| 793 |
+
if (start >= end) {
|
| 794 |
+
break;
|
| 795 |
+
}
|
| 796 |
+
ranges.emplace_back(start, end);
|
| 797 |
+
}
|
| 798 |
+
orbitquant::cpu::run_ranges(
|
| 799 |
+
ranges,
|
| 800 |
+
[&args, function](std::int64_t start, std::int64_t end) {
|
| 801 |
+
function(args, start, end);
|
| 802 |
+
});
|
| 803 |
+
}
|
| 804 |
+
|
| 805 |
+
} // namespace
|
| 806 |
+
|
| 807 |
+
void matmul_packed_adaln_int4_cpu(
|
| 808 |
+
OrbitQuantTensor &out,
|
| 809 |
+
OrbitQuantTensor const &x,
|
| 810 |
+
OrbitQuantTensor const &packed_weight,
|
| 811 |
+
OrbitQuantTensor const &scales,
|
| 812 |
+
OrbitQuantTensor const &bias,
|
| 813 |
+
bool has_bias,
|
| 814 |
+
int64_t out_features,
|
| 815 |
+
int64_t in_features,
|
| 816 |
+
int64_t group_size) {
|
| 817 |
+
using torch::headeronly::DeviceType;
|
| 818 |
+
using torch::headeronly::ScalarType;
|
| 819 |
+
|
| 820 |
+
STD_TORCH_CHECK(x.device().type() == DeviceType::CPU, "x must be a CPU tensor");
|
| 821 |
+
STD_TORCH_CHECK(out.device().type() == DeviceType::CPU, "out must be a CPU tensor");
|
| 822 |
+
STD_TORCH_CHECK(
|
| 823 |
+
packed_weight.device().type() == DeviceType::CPU,
|
| 824 |
+
"packed_weight must be a CPU tensor");
|
| 825 |
+
STD_TORCH_CHECK(
|
| 826 |
+
scales.device().type() == DeviceType::CPU,
|
| 827 |
+
"scales must be a CPU tensor");
|
| 828 |
+
STD_TORCH_CHECK(x.is_contiguous(), "x must be contiguous");
|
| 829 |
+
STD_TORCH_CHECK(out.is_contiguous(), "out must be contiguous");
|
| 830 |
+
STD_TORCH_CHECK(packed_weight.is_contiguous(), "packed_weight must be contiguous");
|
| 831 |
+
STD_TORCH_CHECK(scales.is_contiguous(), "scales must be contiguous");
|
| 832 |
+
STD_TORCH_CHECK(x.scalar_type() == ScalarType::BFloat16, "x must be bfloat16");
|
| 833 |
+
STD_TORCH_CHECK(out.scalar_type() == ScalarType::BFloat16, "out must be bfloat16");
|
| 834 |
+
STD_TORCH_CHECK(
|
| 835 |
+
packed_weight.scalar_type() == ScalarType::Byte,
|
| 836 |
+
"packed_weight must be uint8");
|
| 837 |
+
STD_TORCH_CHECK(scales.scalar_type() == ScalarType::Float, "scales must be float32");
|
| 838 |
+
STD_TORCH_CHECK(x.dim() == 2, "x must be rank 2");
|
| 839 |
+
STD_TORCH_CHECK(out.dim() == 2, "out must be rank 2");
|
| 840 |
+
STD_TORCH_CHECK(group_size > 0, "group_size must be positive");
|
| 841 |
+
STD_TORCH_CHECK(x.size(1) == in_features, "x has an unexpected input dimension");
|
| 842 |
+
STD_TORCH_CHECK(out.size(0) == x.size(0), "out has an unexpected row count");
|
| 843 |
+
STD_TORCH_CHECK(out.size(1) == out_features, "out has an unexpected output dimension");
|
| 844 |
+
const int64_t num_groups = (in_features + group_size - 1) / group_size;
|
| 845 |
+
const int64_t padded_in_features = num_groups * group_size;
|
| 846 |
+
const int64_t packed_values = out_features * padded_in_features;
|
| 847 |
+
STD_TORCH_CHECK(
|
| 848 |
+
packed_weight.numel() >= (packed_values + 1) / 2,
|
| 849 |
+
"packed_weight is too short");
|
| 850 |
+
STD_TORCH_CHECK(
|
| 851 |
+
scales.numel() == out_features * num_groups,
|
| 852 |
+
"scales must match out_features and num_groups");
|
| 853 |
+
if (has_bias) {
|
| 854 |
+
STD_TORCH_CHECK(bias.device().type() == DeviceType::CPU, "bias must be a CPU tensor");
|
| 855 |
+
STD_TORCH_CHECK(bias.is_contiguous(), "bias must be contiguous");
|
| 856 |
+
STD_TORCH_CHECK(bias.scalar_type() == ScalarType::Float, "bias must be float32");
|
| 857 |
+
STD_TORCH_CHECK(bias.numel() == out_features, "bias must match out_features");
|
| 858 |
+
}
|
| 859 |
+
if (x.numel() == 0 || out_features == 0) {
|
| 860 |
+
return;
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
const AdalnArgs args{
|
| 864 |
+
out.mutable_data_ptr(),
|
| 865 |
+
x.const_data_ptr(),
|
| 866 |
+
packed_weight.const_data_ptr<std::uint8_t>(),
|
| 867 |
+
scales.const_data_ptr<float>(),
|
| 868 |
+
has_bias ? bias.const_data_ptr<float>() : nullptr,
|
| 869 |
+
has_bias,
|
| 870 |
+
x.size(0),
|
| 871 |
+
out_features,
|
| 872 |
+
in_features,
|
| 873 |
+
group_size,
|
| 874 |
+
num_groups,
|
| 875 |
+
padded_in_features,
|
| 876 |
+
};
|
| 877 |
+
parallel_packed_adaln(args, select_adaln_kernel());
|
| 878 |
+
}
|
orbitquant_packed_matmul_cpu/packed_matmul_cpu.cpp
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "cpu_pool.h"
|
| 2 |
+
#include "cpu_threads.h"
|
| 3 |
+
#include "packed_matmul_cpu.h"
|
| 4 |
+
#include "../torch-ext/torch_binding.h"
|
| 5 |
+
|
| 6 |
+
#include <torch/headeronly/core/DeviceType.h>
|
| 7 |
+
#include <torch/headeronly/core/ScalarType.h>
|
| 8 |
+
#include <torch/headeronly/macros/Macros.h>
|
| 9 |
+
|
| 10 |
+
#include <algorithm>
|
| 11 |
+
#include <cstdlib>
|
| 12 |
+
#include <cstring>
|
| 13 |
+
#include <utility>
|
| 14 |
+
#include <vector>
|
| 15 |
+
|
| 16 |
+
namespace {
|
| 17 |
+
|
| 18 |
+
orbitquant::cpu::ScalarKind scalar_kind(OrbitQuantTensor const &tensor) {
|
| 19 |
+
using torch::headeronly::ScalarType;
|
| 20 |
+
switch (tensor.scalar_type()) {
|
| 21 |
+
case ScalarType::Float:
|
| 22 |
+
return orbitquant::cpu::ScalarKind::Float32;
|
| 23 |
+
case ScalarType::Half:
|
| 24 |
+
return orbitquant::cpu::ScalarKind::Float16;
|
| 25 |
+
case ScalarType::BFloat16:
|
| 26 |
+
return orbitquant::cpu::ScalarKind::BFloat16;
|
| 27 |
+
default:
|
| 28 |
+
STD_TORCH_CHECK(
|
| 29 |
+
false,
|
| 30 |
+
"CPU packed matmul supports float32, float16, and bfloat16 inputs");
|
| 31 |
+
}
|
| 32 |
+
return orbitquant::cpu::ScalarKind::Float32;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
void parallel_packed_matmul(
|
| 36 |
+
orbitquant::cpu::PackedMatmulArgs const &args,
|
| 37 |
+
orbitquant::cpu::PackedMatmulRangeFn function) {
|
| 38 |
+
const std::int64_t arithmetic =
|
| 39 |
+
args.rows * args.out_features * args.in_features;
|
| 40 |
+
const int max_threads = orbitquant::cpu::requested_threads();
|
| 41 |
+
const int threads = arithmetic < 1'000'000
|
| 42 |
+
? 1
|
| 43 |
+
: std::max<int>(
|
| 44 |
+
1,
|
| 45 |
+
std::min<std::int64_t>(max_threads, args.out_features / 16));
|
| 46 |
+
if (threads == 1) {
|
| 47 |
+
function(args, 0, args.out_features);
|
| 48 |
+
return;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
std::vector<std::pair<std::int64_t, std::int64_t>> ranges;
|
| 52 |
+
ranges.reserve(threads);
|
| 53 |
+
const std::int64_t columns_per_thread =
|
| 54 |
+
(args.out_features + threads - 1) / threads;
|
| 55 |
+
for (int thread = 0; thread < threads; ++thread) {
|
| 56 |
+
const std::int64_t start = thread * columns_per_thread;
|
| 57 |
+
const std::int64_t end = std::min(
|
| 58 |
+
args.out_features,
|
| 59 |
+
start + columns_per_thread);
|
| 60 |
+
if (start >= end) {
|
| 61 |
+
break;
|
| 62 |
+
}
|
| 63 |
+
ranges.emplace_back(start, end);
|
| 64 |
+
}
|
| 65 |
+
orbitquant::cpu::run_ranges(
|
| 66 |
+
ranges,
|
| 67 |
+
[&args, function](std::int64_t start, std::int64_t end) {
|
| 68 |
+
function(args, start, end);
|
| 69 |
+
});
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
orbitquant::cpu::PackedMatmulRangeFn select_packed_matmul() {
|
| 73 |
+
const char *requested = std::getenv("ORBITQUANT_CPU_ISA");
|
| 74 |
+
if (requested == nullptr || std::strcmp(requested, "auto") == 0) {
|
| 75 |
+
if (orbitquant::cpu::packed_matmul_x86_avx512_available()) {
|
| 76 |
+
return orbitquant::cpu::packed_matmul_x86_avx512_range;
|
| 77 |
+
}
|
| 78 |
+
if (orbitquant::cpu::packed_matmul_x86_avx2_available()) {
|
| 79 |
+
return orbitquant::cpu::packed_matmul_x86_avx2_range;
|
| 80 |
+
}
|
| 81 |
+
if (orbitquant::cpu::packed_matmul_neon_available()) {
|
| 82 |
+
return orbitquant::cpu::packed_matmul_neon_range;
|
| 83 |
+
}
|
| 84 |
+
return orbitquant::cpu::packed_matmul_scalar_range;
|
| 85 |
+
}
|
| 86 |
+
if (std::strcmp(requested, "scalar") == 0) {
|
| 87 |
+
return orbitquant::cpu::packed_matmul_scalar_range;
|
| 88 |
+
}
|
| 89 |
+
if (std::strcmp(requested, "avx2") == 0) {
|
| 90 |
+
STD_TORCH_CHECK(
|
| 91 |
+
orbitquant::cpu::packed_matmul_x86_avx2_available(),
|
| 92 |
+
"ORBITQUANT_CPU_ISA=avx2 requested AVX2/FMA/F16C on an unsupported CPU");
|
| 93 |
+
return orbitquant::cpu::packed_matmul_x86_avx2_range;
|
| 94 |
+
}
|
| 95 |
+
if (std::strcmp(requested, "avx512") == 0) {
|
| 96 |
+
STD_TORCH_CHECK(
|
| 97 |
+
orbitquant::cpu::packed_matmul_x86_avx512_available(),
|
| 98 |
+
"ORBITQUANT_CPU_ISA=avx512 requested AVX-512F/DQ/BW/VL on an unsupported CPU");
|
| 99 |
+
return orbitquant::cpu::packed_matmul_x86_avx512_range;
|
| 100 |
+
}
|
| 101 |
+
if (std::strcmp(requested, "neon") == 0) {
|
| 102 |
+
STD_TORCH_CHECK(
|
| 103 |
+
orbitquant::cpu::packed_matmul_neon_available(),
|
| 104 |
+
"ORBITQUANT_CPU_ISA=neon requested NEON on an unsupported CPU");
|
| 105 |
+
return orbitquant::cpu::packed_matmul_neon_range;
|
| 106 |
+
}
|
| 107 |
+
STD_TORCH_CHECK(
|
| 108 |
+
false,
|
| 109 |
+
"ORBITQUANT_CPU_ISA must be auto, scalar, avx2, avx512, or neon");
|
| 110 |
+
return orbitquant::cpu::packed_matmul_scalar_range;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
} // namespace
|
| 114 |
+
|
| 115 |
+
void matmul_packed_weight(
|
| 116 |
+
OrbitQuantTensor &out,
|
| 117 |
+
OrbitQuantTensor const &x,
|
| 118 |
+
OrbitQuantTensor const &packed_weight_indices,
|
| 119 |
+
OrbitQuantTensor const &row_norms,
|
| 120 |
+
OrbitQuantTensor const ¢roids,
|
| 121 |
+
OrbitQuantTensor const &bias,
|
| 122 |
+
bool has_bias,
|
| 123 |
+
int64_t bits,
|
| 124 |
+
int64_t out_features,
|
| 125 |
+
int64_t in_features,
|
| 126 |
+
int64_t block_m,
|
| 127 |
+
int64_t block_n,
|
| 128 |
+
int64_t block_k) {
|
| 129 |
+
using torch::headeronly::DeviceType;
|
| 130 |
+
using torch::headeronly::ScalarType;
|
| 131 |
+
|
| 132 |
+
STD_TORCH_CHECK(x.device().type() == DeviceType::CPU, "x must be a CPU tensor");
|
| 133 |
+
STD_TORCH_CHECK(out.device().type() == DeviceType::CPU, "out must be a CPU tensor");
|
| 134 |
+
STD_TORCH_CHECK(
|
| 135 |
+
packed_weight_indices.device().type() == DeviceType::CPU,
|
| 136 |
+
"packed weights must be CPU tensors");
|
| 137 |
+
STD_TORCH_CHECK(
|
| 138 |
+
row_norms.device().type() == DeviceType::CPU,
|
| 139 |
+
"row norms must be CPU tensors");
|
| 140 |
+
STD_TORCH_CHECK(
|
| 141 |
+
centroids.device().type() == DeviceType::CPU,
|
| 142 |
+
"centroids must be CPU tensors");
|
| 143 |
+
STD_TORCH_CHECK(x.is_contiguous(), "x must be contiguous");
|
| 144 |
+
STD_TORCH_CHECK(out.is_contiguous(), "out must be contiguous");
|
| 145 |
+
STD_TORCH_CHECK(
|
| 146 |
+
packed_weight_indices.is_contiguous(), "packed weights must be contiguous");
|
| 147 |
+
STD_TORCH_CHECK(row_norms.is_contiguous(), "row norms must be contiguous");
|
| 148 |
+
STD_TORCH_CHECK(centroids.is_contiguous(), "centroids must be contiguous");
|
| 149 |
+
STD_TORCH_CHECK(
|
| 150 |
+
packed_weight_indices.scalar_type() == ScalarType::Byte,
|
| 151 |
+
"packed weights must be uint8");
|
| 152 |
+
STD_TORCH_CHECK(
|
| 153 |
+
row_norms.scalar_type() == ScalarType::Float,
|
| 154 |
+
"row_norms must be float32");
|
| 155 |
+
STD_TORCH_CHECK(
|
| 156 |
+
centroids.scalar_type() == ScalarType::Float,
|
| 157 |
+
"centroids must be float32");
|
| 158 |
+
STD_TORCH_CHECK(out.scalar_type() == x.scalar_type(), "out dtype must match x dtype");
|
| 159 |
+
STD_TORCH_CHECK(x.dim() == 2, "x must be rank 2");
|
| 160 |
+
STD_TORCH_CHECK(out.dim() == 2, "out must be rank 2");
|
| 161 |
+
STD_TORCH_CHECK(bits > 0 && bits <= 8, "bits must be in [1, 8]");
|
| 162 |
+
STD_TORCH_CHECK(block_m > 0 && block_n > 0 && block_k > 0, "tile sizes must be positive");
|
| 163 |
+
STD_TORCH_CHECK(x.size(1) == in_features, "x has an unexpected input dimension");
|
| 164 |
+
STD_TORCH_CHECK(out.size(0) == x.size(0), "out has an unexpected row count");
|
| 165 |
+
STD_TORCH_CHECK(out.size(1) == out_features, "out has an unexpected output dimension");
|
| 166 |
+
STD_TORCH_CHECK(row_norms.numel() == out_features, "row_norms must match out_features");
|
| 167 |
+
STD_TORCH_CHECK(centroids.numel() >= (1LL << bits), "centroids are too short");
|
| 168 |
+
const int64_t packed_bytes = (out_features * in_features * bits + 7) / 8;
|
| 169 |
+
STD_TORCH_CHECK(
|
| 170 |
+
packed_weight_indices.numel() >= packed_bytes,
|
| 171 |
+
"packed weights are too short");
|
| 172 |
+
if (has_bias) {
|
| 173 |
+
STD_TORCH_CHECK(
|
| 174 |
+
bias.device().type() == DeviceType::CPU,
|
| 175 |
+
"bias must be a CPU tensor");
|
| 176 |
+
STD_TORCH_CHECK(bias.is_contiguous(), "bias must be contiguous");
|
| 177 |
+
STD_TORCH_CHECK(
|
| 178 |
+
bias.scalar_type() == ScalarType::Float,
|
| 179 |
+
"bias must be float32");
|
| 180 |
+
STD_TORCH_CHECK(bias.numel() == out_features, "bias must match out_features");
|
| 181 |
+
}
|
| 182 |
+
if (x.numel() == 0 || out_features == 0) {
|
| 183 |
+
return;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
orbitquant::cpu::PackedMatmulArgs args{
|
| 187 |
+
out.mutable_data_ptr(),
|
| 188 |
+
x.const_data_ptr(),
|
| 189 |
+
packed_weight_indices.const_data_ptr<std::uint8_t>(),
|
| 190 |
+
row_norms.const_data_ptr<float>(),
|
| 191 |
+
centroids.const_data_ptr<float>(),
|
| 192 |
+
has_bias ? bias.const_data_ptr<float>() : nullptr,
|
| 193 |
+
has_bias,
|
| 194 |
+
scalar_kind(x),
|
| 195 |
+
x.size(0),
|
| 196 |
+
out_features,
|
| 197 |
+
in_features,
|
| 198 |
+
bits,
|
| 199 |
+
};
|
| 200 |
+
parallel_packed_matmul(args, select_packed_matmul());
|
| 201 |
+
}
|
orbitquant_packed_matmul_cpu/packed_matmul_cpu.h
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
#include <cstdint>
|
| 4 |
+
|
| 5 |
+
namespace orbitquant::cpu {
|
| 6 |
+
|
| 7 |
+
enum class ScalarKind : std::uint8_t {
|
| 8 |
+
Float32,
|
| 9 |
+
Float16,
|
| 10 |
+
BFloat16,
|
| 11 |
+
};
|
| 12 |
+
|
| 13 |
+
struct PackedMatmulArgs {
|
| 14 |
+
void *out;
|
| 15 |
+
void const *x;
|
| 16 |
+
std::uint8_t const *packed_weight_indices;
|
| 17 |
+
float const *row_norms;
|
| 18 |
+
float const *centroids;
|
| 19 |
+
float const *bias;
|
| 20 |
+
bool has_bias;
|
| 21 |
+
ScalarKind scalar_kind;
|
| 22 |
+
std::int64_t rows;
|
| 23 |
+
std::int64_t out_features;
|
| 24 |
+
std::int64_t in_features;
|
| 25 |
+
std::int64_t bits;
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
using PackedMatmulRangeFn = void (*)(
|
| 29 |
+
PackedMatmulArgs const &args,
|
| 30 |
+
std::int64_t out_start,
|
| 31 |
+
std::int64_t out_end);
|
| 32 |
+
|
| 33 |
+
void packed_matmul_scalar_range(
|
| 34 |
+
PackedMatmulArgs const &args,
|
| 35 |
+
std::int64_t out_start,
|
| 36 |
+
std::int64_t out_end);
|
| 37 |
+
|
| 38 |
+
bool packed_matmul_neon_available();
|
| 39 |
+
|
| 40 |
+
void packed_matmul_neon_range(
|
| 41 |
+
PackedMatmulArgs const &args,
|
| 42 |
+
std::int64_t out_start,
|
| 43 |
+
std::int64_t out_end);
|
| 44 |
+
|
| 45 |
+
bool packed_matmul_x86_avx2_available();
|
| 46 |
+
|
| 47 |
+
void packed_matmul_x86_avx2_range(
|
| 48 |
+
PackedMatmulArgs const &args,
|
| 49 |
+
std::int64_t out_start,
|
| 50 |
+
std::int64_t out_end);
|
| 51 |
+
|
| 52 |
+
bool packed_matmul_x86_avx512_available();
|
| 53 |
+
|
| 54 |
+
void packed_matmul_x86_avx512_range(
|
| 55 |
+
PackedMatmulArgs const &args,
|
| 56 |
+
std::int64_t out_start,
|
| 57 |
+
std::int64_t out_end);
|
| 58 |
+
|
| 59 |
+
} // namespace orbitquant::cpu
|
orbitquant_packed_matmul_cpu/packed_matmul_neon.cpp
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "packed_matmul_cpu.h"
|
| 2 |
+
|
| 3 |
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
| 4 |
+
#include <arm_neon.h>
|
| 5 |
+
|
| 6 |
+
#include <torch/headeronly/util/BFloat16.h>
|
| 7 |
+
#include <torch/headeronly/util/Half.h>
|
| 8 |
+
|
| 9 |
+
#include <type_traits>
|
| 10 |
+
#include <vector>
|
| 11 |
+
|
| 12 |
+
namespace orbitquant::cpu {
|
| 13 |
+
namespace {
|
| 14 |
+
|
| 15 |
+
inline float horizontal_sum(float32x4_t value) {
|
| 16 |
+
#if defined(__aarch64__)
|
| 17 |
+
return vaddvq_f32(value);
|
| 18 |
+
#else
|
| 19 |
+
const float32x2_t pair = vadd_f32(vget_low_f32(value), vget_high_f32(value));
|
| 20 |
+
return vget_lane_f32(vpadd_f32(pair, pair), 0);
|
| 21 |
+
#endif
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
inline float32x4_t load_float4(void const *data, std::int64_t offset) {
|
| 25 |
+
return vld1q_f32(static_cast<float const *>(data) + offset);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
inline float32x4_t load_half4(void const *data, std::int64_t offset) {
|
| 29 |
+
const auto *source = reinterpret_cast<float16_t const *>(
|
| 30 |
+
static_cast<std::uint16_t const *>(data) + offset);
|
| 31 |
+
return vcvt_f32_f16(vld1_f16(source));
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
inline float32x4_t load_bfloat4(void const *data, std::int64_t offset) {
|
| 35 |
+
const uint16x4_t raw = vld1_u16(
|
| 36 |
+
static_cast<std::uint16_t const *>(data) + offset);
|
| 37 |
+
return vreinterpretq_f32_u32(vshlq_n_u32(vmovl_u16(raw), 16));
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
template <typename scalar_t>
|
| 41 |
+
inline void store_value(void *data, std::int64_t offset, float value) {
|
| 42 |
+
static_cast<scalar_t *>(data)[offset] = scalar_t(value);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
template <>
|
| 46 |
+
inline void store_value<float>(void *data, std::int64_t offset, float value) {
|
| 47 |
+
static_cast<float *>(data)[offset] = value;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
template <
|
| 51 |
+
typename scalar_t,
|
| 52 |
+
float32x4_t (*load4)(void const *, std::int64_t),
|
| 53 |
+
int row_tile>
|
| 54 |
+
inline void packed_matmul_neon_w4_rows(
|
| 55 |
+
PackedMatmulArgs const &args,
|
| 56 |
+
std::uint8_t const *packed_row,
|
| 57 |
+
std::int64_t out_col,
|
| 58 |
+
std::int64_t row_start) {
|
| 59 |
+
float32x4_t accumulator0[row_tile];
|
| 60 |
+
float32x4_t accumulator1[row_tile];
|
| 61 |
+
#pragma clang loop unroll(full)
|
| 62 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 63 |
+
accumulator0[row] = vdupq_n_f32(0.0f);
|
| 64 |
+
accumulator1[row] = vdupq_n_f32(0.0f);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
std::int64_t k = 0;
|
| 68 |
+
for (; k + 8 <= args.in_features; k += 8) {
|
| 69 |
+
const std::int64_t byte_offset = k / 2;
|
| 70 |
+
float weights0[4];
|
| 71 |
+
float weights1[4];
|
| 72 |
+
#pragma clang loop unroll(full)
|
| 73 |
+
for (int pair = 0; pair < 4; ++pair) {
|
| 74 |
+
const std::uint8_t packed = packed_row[byte_offset + pair];
|
| 75 |
+
const int value = pair * 2;
|
| 76 |
+
if (value < 4) {
|
| 77 |
+
weights0[value] = args.centroids[packed & 15u];
|
| 78 |
+
weights0[value + 1] = args.centroids[(packed >> 4) & 15u];
|
| 79 |
+
} else {
|
| 80 |
+
weights1[value - 4] = args.centroids[packed & 15u];
|
| 81 |
+
weights1[value - 3] = args.centroids[(packed >> 4) & 15u];
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
const float32x4_t weight0 = vld1q_f32(weights0);
|
| 85 |
+
const float32x4_t weight1 = vld1q_f32(weights1);
|
| 86 |
+
#pragma clang loop unroll(full)
|
| 87 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 88 |
+
const std::int64_t input_offset =
|
| 89 |
+
(row_start + row) * args.in_features + k;
|
| 90 |
+
accumulator0[row] =
|
| 91 |
+
vfmaq_f32(accumulator0[row], load4(args.x, input_offset), weight0);
|
| 92 |
+
accumulator1[row] = vfmaq_f32(
|
| 93 |
+
accumulator1[row], load4(args.x, input_offset + 4), weight1);
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
const float row_norm = args.row_norms[out_col];
|
| 98 |
+
#pragma clang loop unroll(full)
|
| 99 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 100 |
+
const std::int64_t input_row_offset =
|
| 101 |
+
(row_start + row) * args.in_features;
|
| 102 |
+
float accumulator =
|
| 103 |
+
horizontal_sum(vaddq_f32(accumulator0[row], accumulator1[row]));
|
| 104 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 105 |
+
const std::uint8_t packed = packed_row[tail / 2];
|
| 106 |
+
const std::uint8_t index =
|
| 107 |
+
(tail & 1) == 0 ? packed & 15u : (packed >> 4) & 15u;
|
| 108 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 109 |
+
accumulator +=
|
| 110 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 111 |
+
args.centroids[index];
|
| 112 |
+
} else {
|
| 113 |
+
accumulator += static_cast<float>(
|
| 114 |
+
static_cast<scalar_t const *>(
|
| 115 |
+
args.x)[input_row_offset + tail]) *
|
| 116 |
+
args.centroids[index];
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
accumulator *= row_norm;
|
| 120 |
+
if (args.has_bias) {
|
| 121 |
+
accumulator += args.bias[out_col];
|
| 122 |
+
}
|
| 123 |
+
store_value<scalar_t>(
|
| 124 |
+
args.out,
|
| 125 |
+
(row_start + row) * args.out_features + out_col,
|
| 126 |
+
accumulator);
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
template <int Bits>
|
| 131 |
+
inline std::uint32_t unpack_index_generic(
|
| 132 |
+
std::uint8_t const *packed_row,
|
| 133 |
+
std::int64_t value_index) {
|
| 134 |
+
const std::int64_t bit_start = value_index * Bits;
|
| 135 |
+
const std::int64_t byte_index = bit_start >> 3;
|
| 136 |
+
const unsigned bit_offset = static_cast<unsigned>(bit_start & 7);
|
| 137 |
+
std::uint32_t raw = packed_row[byte_index];
|
| 138 |
+
if (bit_offset + static_cast<unsigned>(Bits) > 8) {
|
| 139 |
+
raw |= static_cast<std::uint32_t>(packed_row[byte_index + 1]) << 8;
|
| 140 |
+
}
|
| 141 |
+
return (raw >> bit_offset) & ((1u << Bits) - 1u);
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
template <
|
| 145 |
+
typename scalar_t,
|
| 146 |
+
float32x4_t (*load4)(void const *, std::int64_t),
|
| 147 |
+
int Bits,
|
| 148 |
+
int row_tile>
|
| 149 |
+
inline void packed_matmul_neon_lowbit_rows(
|
| 150 |
+
PackedMatmulArgs const &args,
|
| 151 |
+
std::uint8_t const *packed_row,
|
| 152 |
+
std::int64_t out_col,
|
| 153 |
+
std::int64_t row_start) {
|
| 154 |
+
float32x4_t accumulator0[row_tile];
|
| 155 |
+
float32x4_t accumulator1[row_tile];
|
| 156 |
+
#pragma clang loop unroll(full)
|
| 157 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 158 |
+
accumulator0[row] = vdupq_n_f32(0.0f);
|
| 159 |
+
accumulator1[row] = vdupq_n_f32(0.0f);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
std::int64_t k = 0;
|
| 163 |
+
for (; k + 8 <= args.in_features; k += 8) {
|
| 164 |
+
float weights0[4];
|
| 165 |
+
float weights1[4];
|
| 166 |
+
#pragma clang loop unroll(full)
|
| 167 |
+
for (int value = 0; value < 4; ++value) {
|
| 168 |
+
weights0[value] =
|
| 169 |
+
args.centroids[unpack_index_generic<Bits>(packed_row, k + value)];
|
| 170 |
+
weights1[value] =
|
| 171 |
+
args.centroids[unpack_index_generic<Bits>(packed_row, k + 4 + value)];
|
| 172 |
+
}
|
| 173 |
+
const float32x4_t weight0 = vld1q_f32(weights0);
|
| 174 |
+
const float32x4_t weight1 = vld1q_f32(weights1);
|
| 175 |
+
#pragma clang loop unroll(full)
|
| 176 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 177 |
+
const std::int64_t input_offset =
|
| 178 |
+
(row_start + row) * args.in_features + k;
|
| 179 |
+
accumulator0[row] =
|
| 180 |
+
vfmaq_f32(accumulator0[row], load4(args.x, input_offset), weight0);
|
| 181 |
+
accumulator1[row] = vfmaq_f32(
|
| 182 |
+
accumulator1[row], load4(args.x, input_offset + 4), weight1);
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
const float row_norm = args.row_norms[out_col];
|
| 187 |
+
#pragma clang loop unroll(full)
|
| 188 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 189 |
+
const std::int64_t input_row_offset =
|
| 190 |
+
(row_start + row) * args.in_features;
|
| 191 |
+
float accumulator =
|
| 192 |
+
horizontal_sum(vaddq_f32(accumulator0[row], accumulator1[row]));
|
| 193 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 194 |
+
const std::uint32_t index =
|
| 195 |
+
unpack_index_generic<Bits>(packed_row, tail);
|
| 196 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 197 |
+
accumulator +=
|
| 198 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 199 |
+
args.centroids[index];
|
| 200 |
+
} else {
|
| 201 |
+
accumulator += static_cast<float>(
|
| 202 |
+
static_cast<scalar_t const *>(
|
| 203 |
+
args.x)[input_row_offset + tail]) *
|
| 204 |
+
args.centroids[index];
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
accumulator *= row_norm;
|
| 208 |
+
if (args.has_bias) {
|
| 209 |
+
accumulator += args.bias[out_col];
|
| 210 |
+
}
|
| 211 |
+
store_value<scalar_t>(
|
| 212 |
+
args.out,
|
| 213 |
+
(row_start + row) * args.out_features + out_col,
|
| 214 |
+
accumulator);
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
template <typename scalar_t, float32x4_t (*load4)(void const *, std::int64_t), int row_tile>
|
| 219 |
+
inline void packed_matmul_neon_buffered_rows(
|
| 220 |
+
PackedMatmulArgs const &args,
|
| 221 |
+
float const *decoded_row,
|
| 222 |
+
std::int64_t out_col,
|
| 223 |
+
std::int64_t row_start) {
|
| 224 |
+
float32x4_t accumulator0[row_tile];
|
| 225 |
+
float32x4_t accumulator1[row_tile];
|
| 226 |
+
#pragma clang loop unroll(full)
|
| 227 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 228 |
+
accumulator0[row] = vdupq_n_f32(0.0f);
|
| 229 |
+
accumulator1[row] = vdupq_n_f32(0.0f);
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
std::int64_t k = 0;
|
| 233 |
+
for (; k + 8 <= args.in_features; k += 8) {
|
| 234 |
+
const float32x4_t weight0 = vld1q_f32(decoded_row + k);
|
| 235 |
+
const float32x4_t weight1 = vld1q_f32(decoded_row + k + 4);
|
| 236 |
+
#pragma clang loop unroll(full)
|
| 237 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 238 |
+
const std::int64_t input_offset =
|
| 239 |
+
(row_start + row) * args.in_features + k;
|
| 240 |
+
accumulator0[row] =
|
| 241 |
+
vfmaq_f32(accumulator0[row], load4(args.x, input_offset), weight0);
|
| 242 |
+
accumulator1[row] = vfmaq_f32(
|
| 243 |
+
accumulator1[row], load4(args.x, input_offset + 4), weight1);
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
const float row_norm = args.row_norms[out_col];
|
| 248 |
+
#pragma clang loop unroll(full)
|
| 249 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 250 |
+
const std::int64_t input_row_offset =
|
| 251 |
+
(row_start + row) * args.in_features;
|
| 252 |
+
float accumulator =
|
| 253 |
+
horizontal_sum(vaddq_f32(accumulator0[row], accumulator1[row]));
|
| 254 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 255 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 256 |
+
accumulator +=
|
| 257 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 258 |
+
decoded_row[tail];
|
| 259 |
+
} else {
|
| 260 |
+
accumulator += static_cast<float>(
|
| 261 |
+
static_cast<scalar_t const *>(
|
| 262 |
+
args.x)[input_row_offset + tail]) *
|
| 263 |
+
decoded_row[tail];
|
| 264 |
+
}
|
| 265 |
+
}
|
| 266 |
+
accumulator *= row_norm;
|
| 267 |
+
if (args.has_bias) {
|
| 268 |
+
accumulator += args.bias[out_col];
|
| 269 |
+
}
|
| 270 |
+
store_value<scalar_t>(
|
| 271 |
+
args.out,
|
| 272 |
+
(row_start + row) * args.out_features + out_col,
|
| 273 |
+
accumulator);
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
template <
|
| 278 |
+
typename scalar_t,
|
| 279 |
+
float32x4_t (*load4)(void const *, std::int64_t),
|
| 280 |
+
int Bits>
|
| 281 |
+
void packed_matmul_neon_lowbit_typed(
|
| 282 |
+
PackedMatmulArgs const &args,
|
| 283 |
+
std::int64_t out_start,
|
| 284 |
+
std::int64_t out_end) {
|
| 285 |
+
const std::int64_t packed_row_bytes = args.in_features * Bits / 8;
|
| 286 |
+
// The NEON decode goes through scalar table lookups, so expanding the
|
| 287 |
+
// column once per >=16-row call amortizes the costliest stage.
|
| 288 |
+
const bool use_decoded_buffer = args.rows >= 16;
|
| 289 |
+
thread_local std::vector<float> decoded_row_storage;
|
| 290 |
+
if (use_decoded_buffer &&
|
| 291 |
+
decoded_row_storage.size() < static_cast<std::size_t>(args.in_features)) {
|
| 292 |
+
decoded_row_storage.resize(static_cast<std::size_t>(args.in_features));
|
| 293 |
+
}
|
| 294 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 295 |
+
const auto *packed_row =
|
| 296 |
+
args.packed_weight_indices + out_col * packed_row_bytes;
|
| 297 |
+
if (use_decoded_buffer) {
|
| 298 |
+
float *decoded_row = decoded_row_storage.data();
|
| 299 |
+
for (std::int64_t k = 0; k < args.in_features; ++k) {
|
| 300 |
+
decoded_row[k] =
|
| 301 |
+
args.centroids[unpack_index_generic<Bits>(packed_row, k)];
|
| 302 |
+
}
|
| 303 |
+
std::int64_t row = 0;
|
| 304 |
+
for (; row + 8 <= args.rows; row += 8) {
|
| 305 |
+
packed_matmul_neon_buffered_rows<scalar_t, load4, 8>(
|
| 306 |
+
args, decoded_row, out_col, row);
|
| 307 |
+
}
|
| 308 |
+
for (; row + 4 <= args.rows; row += 4) {
|
| 309 |
+
packed_matmul_neon_buffered_rows<scalar_t, load4, 4>(
|
| 310 |
+
args, decoded_row, out_col, row);
|
| 311 |
+
}
|
| 312 |
+
switch (args.rows - row) {
|
| 313 |
+
case 3:
|
| 314 |
+
packed_matmul_neon_buffered_rows<scalar_t, load4, 3>(
|
| 315 |
+
args, decoded_row, out_col, row);
|
| 316 |
+
break;
|
| 317 |
+
case 2:
|
| 318 |
+
packed_matmul_neon_buffered_rows<scalar_t, load4, 2>(
|
| 319 |
+
args, decoded_row, out_col, row);
|
| 320 |
+
break;
|
| 321 |
+
case 1:
|
| 322 |
+
packed_matmul_neon_buffered_rows<scalar_t, load4, 1>(
|
| 323 |
+
args, decoded_row, out_col, row);
|
| 324 |
+
break;
|
| 325 |
+
default:
|
| 326 |
+
break;
|
| 327 |
+
}
|
| 328 |
+
continue;
|
| 329 |
+
}
|
| 330 |
+
std::int64_t row = 0;
|
| 331 |
+
for (; row + 8 <= args.rows; row += 8) {
|
| 332 |
+
packed_matmul_neon_lowbit_rows<scalar_t, load4, Bits, 8>(
|
| 333 |
+
args, packed_row, out_col, row);
|
| 334 |
+
}
|
| 335 |
+
for (; row + 4 <= args.rows; row += 4) {
|
| 336 |
+
packed_matmul_neon_lowbit_rows<scalar_t, load4, Bits, 4>(
|
| 337 |
+
args, packed_row, out_col, row);
|
| 338 |
+
}
|
| 339 |
+
switch (args.rows - row) {
|
| 340 |
+
case 3:
|
| 341 |
+
packed_matmul_neon_lowbit_rows<scalar_t, load4, Bits, 3>(
|
| 342 |
+
args, packed_row, out_col, row);
|
| 343 |
+
break;
|
| 344 |
+
case 2:
|
| 345 |
+
packed_matmul_neon_lowbit_rows<scalar_t, load4, Bits, 2>(
|
| 346 |
+
args, packed_row, out_col, row);
|
| 347 |
+
break;
|
| 348 |
+
case 1:
|
| 349 |
+
packed_matmul_neon_lowbit_rows<scalar_t, load4, Bits, 1>(
|
| 350 |
+
args, packed_row, out_col, row);
|
| 351 |
+
break;
|
| 352 |
+
default:
|
| 353 |
+
break;
|
| 354 |
+
}
|
| 355 |
+
}
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
template <int Bits>
|
| 359 |
+
void packed_matmul_neon_lowbit_dispatch(
|
| 360 |
+
PackedMatmulArgs const &args,
|
| 361 |
+
std::int64_t out_start,
|
| 362 |
+
std::int64_t out_end) {
|
| 363 |
+
switch (args.scalar_kind) {
|
| 364 |
+
case ScalarKind::Float32:
|
| 365 |
+
packed_matmul_neon_lowbit_typed<float, load_float4, Bits>(
|
| 366 |
+
args, out_start, out_end);
|
| 367 |
+
return;
|
| 368 |
+
case ScalarKind::Float16:
|
| 369 |
+
packed_matmul_neon_lowbit_typed<c10::Half, load_half4, Bits>(
|
| 370 |
+
args, out_start, out_end);
|
| 371 |
+
return;
|
| 372 |
+
case ScalarKind::BFloat16:
|
| 373 |
+
packed_matmul_neon_lowbit_typed<c10::BFloat16, load_bfloat4, Bits>(
|
| 374 |
+
args, out_start, out_end);
|
| 375 |
+
return;
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
template <typename scalar_t, float32x4_t (*load4)(void const *, std::int64_t)>
|
| 380 |
+
void packed_matmul_neon_w4_typed(
|
| 381 |
+
PackedMatmulArgs const &args,
|
| 382 |
+
std::int64_t out_start,
|
| 383 |
+
std::int64_t out_end) {
|
| 384 |
+
constexpr int kPrimaryRowTile = 8;
|
| 385 |
+
const std::int64_t packed_row_bytes = args.in_features / 2;
|
| 386 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 387 |
+
const auto *packed_row =
|
| 388 |
+
args.packed_weight_indices + out_col * packed_row_bytes;
|
| 389 |
+
std::int64_t row = 0;
|
| 390 |
+
if constexpr (kPrimaryRowTile == 8) {
|
| 391 |
+
for (; row + 8 <= args.rows; row += 8) {
|
| 392 |
+
packed_matmul_neon_w4_rows<scalar_t, load4, 8>(
|
| 393 |
+
args, packed_row, out_col, row);
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
for (; row + 4 <= args.rows; row += 4) {
|
| 397 |
+
packed_matmul_neon_w4_rows<scalar_t, load4, 4>(
|
| 398 |
+
args, packed_row, out_col, row);
|
| 399 |
+
}
|
| 400 |
+
switch (args.rows - row) {
|
| 401 |
+
case 3:
|
| 402 |
+
packed_matmul_neon_w4_rows<scalar_t, load4, 3>(
|
| 403 |
+
args, packed_row, out_col, row);
|
| 404 |
+
break;
|
| 405 |
+
case 2:
|
| 406 |
+
packed_matmul_neon_w4_rows<scalar_t, load4, 2>(
|
| 407 |
+
args, packed_row, out_col, row);
|
| 408 |
+
break;
|
| 409 |
+
case 1:
|
| 410 |
+
packed_matmul_neon_w4_rows<scalar_t, load4, 1>(
|
| 411 |
+
args, packed_row, out_col, row);
|
| 412 |
+
break;
|
| 413 |
+
default:
|
| 414 |
+
break;
|
| 415 |
+
}
|
| 416 |
+
}
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
} // namespace
|
| 420 |
+
|
| 421 |
+
bool packed_matmul_neon_available() {
|
| 422 |
+
return true;
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
void packed_matmul_neon_range(
|
| 426 |
+
PackedMatmulArgs const &args,
|
| 427 |
+
std::int64_t out_start,
|
| 428 |
+
std::int64_t out_end) {
|
| 429 |
+
if (args.bits == 4 && args.in_features % 2 == 0 && args.rows >= 16) {
|
| 430 |
+
// The buffered generic path decodes each column once, which beats the
|
| 431 |
+
// per-tile scalar decode as soon as several row tiles reuse it.
|
| 432 |
+
packed_matmul_neon_lowbit_dispatch<4>(args, out_start, out_end);
|
| 433 |
+
return;
|
| 434 |
+
}
|
| 435 |
+
if (args.bits == 2 && args.in_features % 4 == 0) {
|
| 436 |
+
packed_matmul_neon_lowbit_dispatch<2>(args, out_start, out_end);
|
| 437 |
+
return;
|
| 438 |
+
}
|
| 439 |
+
if (args.bits == 3 && args.in_features % 8 == 0) {
|
| 440 |
+
packed_matmul_neon_lowbit_dispatch<3>(args, out_start, out_end);
|
| 441 |
+
return;
|
| 442 |
+
}
|
| 443 |
+
if (args.bits == 6 && args.in_features % 4 == 0) {
|
| 444 |
+
packed_matmul_neon_lowbit_dispatch<6>(args, out_start, out_end);
|
| 445 |
+
return;
|
| 446 |
+
}
|
| 447 |
+
if (args.bits != 4 || args.in_features % 2 != 0) {
|
| 448 |
+
packed_matmul_scalar_range(args, out_start, out_end);
|
| 449 |
+
return;
|
| 450 |
+
}
|
| 451 |
+
switch (args.scalar_kind) {
|
| 452 |
+
case ScalarKind::Float32:
|
| 453 |
+
packed_matmul_neon_w4_typed<float, load_float4>(args, out_start, out_end);
|
| 454 |
+
return;
|
| 455 |
+
case ScalarKind::Float16:
|
| 456 |
+
packed_matmul_neon_w4_typed<c10::Half, load_half4>(args, out_start, out_end);
|
| 457 |
+
return;
|
| 458 |
+
case ScalarKind::BFloat16:
|
| 459 |
+
packed_matmul_neon_w4_typed<c10::BFloat16, load_bfloat4>(
|
| 460 |
+
args, out_start, out_end);
|
| 461 |
+
return;
|
| 462 |
+
}
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
} // namespace orbitquant::cpu
|
| 466 |
+
|
| 467 |
+
#else
|
| 468 |
+
|
| 469 |
+
namespace orbitquant::cpu {
|
| 470 |
+
|
| 471 |
+
bool packed_matmul_neon_available() {
|
| 472 |
+
return false;
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
void packed_matmul_neon_range(
|
| 476 |
+
PackedMatmulArgs const &args,
|
| 477 |
+
std::int64_t out_start,
|
| 478 |
+
std::int64_t out_end) {
|
| 479 |
+
packed_matmul_scalar_range(args, out_start, out_end);
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
} // namespace orbitquant::cpu
|
| 483 |
+
|
| 484 |
+
#endif
|
orbitquant_packed_matmul_cpu/packed_matmul_scalar.cpp
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "packed_matmul_cpu.h"
|
| 2 |
+
|
| 3 |
+
#include <torch/headeronly/util/BFloat16.h>
|
| 4 |
+
#include <torch/headeronly/util/Half.h>
|
| 5 |
+
|
| 6 |
+
#include <cstddef>
|
| 7 |
+
|
| 8 |
+
namespace orbitquant::cpu {
|
| 9 |
+
namespace {
|
| 10 |
+
|
| 11 |
+
template <typename scalar_t>
|
| 12 |
+
inline float load_scalar(void const *data, std::int64_t offset) {
|
| 13 |
+
return static_cast<float>(static_cast<scalar_t const *>(data)[offset]);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
template <typename scalar_t>
|
| 17 |
+
inline void store_scalar(void *data, std::int64_t offset, float value) {
|
| 18 |
+
static_cast<scalar_t *>(data)[offset] = scalar_t(value);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
template <>
|
| 22 |
+
inline float load_scalar<float>(void const *data, std::int64_t offset) {
|
| 23 |
+
return static_cast<float const *>(data)[offset];
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
template <>
|
| 27 |
+
inline void store_scalar<float>(void *data, std::int64_t offset, float value) {
|
| 28 |
+
static_cast<float *>(data)[offset] = value;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
inline std::uint32_t unpack_index(
|
| 32 |
+
std::uint8_t const *packed,
|
| 33 |
+
std::int64_t value_offset,
|
| 34 |
+
std::int64_t bits) {
|
| 35 |
+
const std::int64_t bit_start = value_offset * bits;
|
| 36 |
+
const std::int64_t byte_index = bit_start >> 3;
|
| 37 |
+
const unsigned bit_offset = static_cast<unsigned>(bit_start & 7);
|
| 38 |
+
std::uint32_t raw = packed[byte_index];
|
| 39 |
+
if (bit_offset + static_cast<unsigned>(bits) > 8) {
|
| 40 |
+
raw |= static_cast<std::uint32_t>(packed[byte_index + 1]) << 8;
|
| 41 |
+
}
|
| 42 |
+
return (raw >> bit_offset) & ((1u << static_cast<unsigned>(bits)) - 1u);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
template <typename scalar_t>
|
| 46 |
+
void packed_matmul_scalar_typed(
|
| 47 |
+
PackedMatmulArgs const &args,
|
| 48 |
+
std::int64_t out_start,
|
| 49 |
+
std::int64_t out_end) {
|
| 50 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 51 |
+
const float row_norm = args.row_norms[out_col];
|
| 52 |
+
const std::int64_t weight_row_offset = out_col * args.in_features;
|
| 53 |
+
for (std::int64_t row = 0; row < args.rows; ++row) {
|
| 54 |
+
const std::int64_t input_row_offset = row * args.in_features;
|
| 55 |
+
float accumulator = 0.0f;
|
| 56 |
+
for (std::int64_t k = 0; k < args.in_features; ++k) {
|
| 57 |
+
const std::uint32_t index = unpack_index(
|
| 58 |
+
args.packed_weight_indices,
|
| 59 |
+
weight_row_offset + k,
|
| 60 |
+
args.bits);
|
| 61 |
+
accumulator += load_scalar<scalar_t>(args.x, input_row_offset + k) *
|
| 62 |
+
args.centroids[index];
|
| 63 |
+
}
|
| 64 |
+
accumulator *= row_norm;
|
| 65 |
+
if (args.has_bias) {
|
| 66 |
+
accumulator += args.bias[out_col];
|
| 67 |
+
}
|
| 68 |
+
store_scalar<scalar_t>(
|
| 69 |
+
args.out,
|
| 70 |
+
row * args.out_features + out_col,
|
| 71 |
+
accumulator);
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
} // namespace
|
| 77 |
+
|
| 78 |
+
void packed_matmul_scalar_range(
|
| 79 |
+
PackedMatmulArgs const &args,
|
| 80 |
+
std::int64_t out_start,
|
| 81 |
+
std::int64_t out_end) {
|
| 82 |
+
switch (args.scalar_kind) {
|
| 83 |
+
case ScalarKind::Float32:
|
| 84 |
+
packed_matmul_scalar_typed<float>(args, out_start, out_end);
|
| 85 |
+
return;
|
| 86 |
+
case ScalarKind::Float16:
|
| 87 |
+
packed_matmul_scalar_typed<c10::Half>(args, out_start, out_end);
|
| 88 |
+
return;
|
| 89 |
+
case ScalarKind::BFloat16:
|
| 90 |
+
packed_matmul_scalar_typed<c10::BFloat16>(args, out_start, out_end);
|
| 91 |
+
return;
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
} // namespace orbitquant::cpu
|
orbitquant_packed_matmul_cpu/packed_matmul_x86.cpp
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "packed_matmul_cpu.h"
|
| 2 |
+
|
| 3 |
+
#if defined(__x86_64__) || defined(_M_X64)
|
| 4 |
+
#include <immintrin.h>
|
| 5 |
+
#if defined(_MSC_VER)
|
| 6 |
+
#include <intrin.h>
|
| 7 |
+
#else
|
| 8 |
+
#include <cpuid.h>
|
| 9 |
+
#endif
|
| 10 |
+
|
| 11 |
+
#include <torch/headeronly/util/BFloat16.h>
|
| 12 |
+
#include <torch/headeronly/util/Half.h>
|
| 13 |
+
|
| 14 |
+
#include <cstring>
|
| 15 |
+
#include <cstdint>
|
| 16 |
+
#include <type_traits>
|
| 17 |
+
#include <vector>
|
| 18 |
+
|
| 19 |
+
#if defined(_MSC_VER)
|
| 20 |
+
#define ORBITQUANT_TARGET_AVX2
|
| 21 |
+
#define ORBITQUANT_NOINLINE __declspec(noinline)
|
| 22 |
+
#else
|
| 23 |
+
#define ORBITQUANT_TARGET_AVX2 __attribute__((target("avx2,fma,f16c")))
|
| 24 |
+
#define ORBITQUANT_NOINLINE __attribute__((noinline))
|
| 25 |
+
#endif
|
| 26 |
+
|
| 27 |
+
namespace orbitquant::cpu {
|
| 28 |
+
namespace {
|
| 29 |
+
|
| 30 |
+
ORBITQUANT_TARGET_AVX2 inline float horizontal_sum(__m256 value) {
|
| 31 |
+
const __m128 halves =
|
| 32 |
+
_mm_add_ps(_mm256_castps256_ps128(value), _mm256_extractf128_ps(value, 1));
|
| 33 |
+
const __m128 pairs = _mm_hadd_ps(halves, halves);
|
| 34 |
+
return _mm_cvtss_f32(_mm_hadd_ps(pairs, pairs));
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
ORBITQUANT_TARGET_AVX2 inline __m256 load_float8(
|
| 38 |
+
void const *data,
|
| 39 |
+
std::int64_t offset) {
|
| 40 |
+
return _mm256_loadu_ps(static_cast<float const *>(data) + offset);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
ORBITQUANT_TARGET_AVX2 inline __m256 load_half8(
|
| 44 |
+
void const *data,
|
| 45 |
+
std::int64_t offset) {
|
| 46 |
+
const auto *source = static_cast<std::uint16_t const *>(data) + offset;
|
| 47 |
+
const __m128i packed =
|
| 48 |
+
_mm_loadu_si128(reinterpret_cast<__m128i const *>(source));
|
| 49 |
+
return _mm256_cvtph_ps(packed);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
ORBITQUANT_TARGET_AVX2 inline __m256 load_bfloat8(
|
| 53 |
+
void const *data,
|
| 54 |
+
std::int64_t offset) {
|
| 55 |
+
const auto *source = static_cast<std::uint16_t const *>(data) + offset;
|
| 56 |
+
const __m128i packed =
|
| 57 |
+
_mm_loadu_si128(reinterpret_cast<__m128i const *>(source));
|
| 58 |
+
const __m256i widened = _mm256_cvtepu16_epi32(packed);
|
| 59 |
+
return _mm256_castsi256_ps(_mm256_slli_epi32(widened, 16));
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
template <typename scalar_t>
|
| 63 |
+
inline void store_value(void *data, std::int64_t offset, float value) {
|
| 64 |
+
static_cast<scalar_t *>(data)[offset] = scalar_t(value);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
template <>
|
| 68 |
+
inline void store_value<float>(void *data, std::int64_t offset, float value) {
|
| 69 |
+
static_cast<float *>(data)[offset] = value;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
template <
|
| 73 |
+
typename scalar_t,
|
| 74 |
+
__m256 (*load8)(void const *, std::int64_t),
|
| 75 |
+
int row_tile>
|
| 76 |
+
ORBITQUANT_TARGET_AVX2 inline void packed_matmul_avx2_w4_rows(
|
| 77 |
+
PackedMatmulArgs const &args,
|
| 78 |
+
std::uint8_t const *packed_row,
|
| 79 |
+
std::int64_t out_col,
|
| 80 |
+
std::int64_t row_start) {
|
| 81 |
+
__m256 accumulators[row_tile];
|
| 82 |
+
#pragma clang loop unroll(full)
|
| 83 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 84 |
+
accumulators[row] = _mm256_setzero_ps();
|
| 85 |
+
}
|
| 86 |
+
const __m256 centroid_lut_low = _mm256_loadu_ps(args.centroids);
|
| 87 |
+
const __m256 centroid_lut_high = _mm256_loadu_ps(args.centroids + 8);
|
| 88 |
+
const __m128i nibble_mask = _mm_set1_epi8(15);
|
| 89 |
+
const __m256i low_table_limit = _mm256_set1_epi32(7);
|
| 90 |
+
|
| 91 |
+
std::int64_t k = 0;
|
| 92 |
+
for (; k + 8 <= args.in_features; k += 8) {
|
| 93 |
+
const std::int64_t byte_offset = k / 2;
|
| 94 |
+
std::int32_t packed;
|
| 95 |
+
std::memcpy(&packed, packed_row + byte_offset, sizeof(packed));
|
| 96 |
+
const __m128i bytes = _mm_cvtsi32_si128(packed);
|
| 97 |
+
const __m128i low = _mm_and_si128(bytes, nibble_mask);
|
| 98 |
+
const __m128i high = _mm_and_si128(
|
| 99 |
+
_mm_srli_epi16(bytes, 4),
|
| 100 |
+
nibble_mask);
|
| 101 |
+
const __m256i indices =
|
| 102 |
+
_mm256_cvtepu8_epi32(_mm_unpacklo_epi8(low, high));
|
| 103 |
+
const __m256 low_weights =
|
| 104 |
+
_mm256_permutevar8x32_ps(centroid_lut_low, indices);
|
| 105 |
+
const __m256 high_weights =
|
| 106 |
+
_mm256_permutevar8x32_ps(centroid_lut_high, indices);
|
| 107 |
+
const __m256 weight = _mm256_blendv_ps(
|
| 108 |
+
low_weights,
|
| 109 |
+
high_weights,
|
| 110 |
+
_mm256_castsi256_ps(_mm256_cmpgt_epi32(indices, low_table_limit)));
|
| 111 |
+
#pragma clang loop unroll(full)
|
| 112 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 113 |
+
const std::int64_t input_offset =
|
| 114 |
+
(row_start + row) * args.in_features + k;
|
| 115 |
+
accumulators[row] = _mm256_fmadd_ps(
|
| 116 |
+
load8(args.x, input_offset),
|
| 117 |
+
weight,
|
| 118 |
+
accumulators[row]);
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
const float row_norm = args.row_norms[out_col];
|
| 123 |
+
#pragma clang loop unroll(full)
|
| 124 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 125 |
+
const std::int64_t input_row_offset =
|
| 126 |
+
(row_start + row) * args.in_features;
|
| 127 |
+
float accumulator = horizontal_sum(accumulators[row]);
|
| 128 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 129 |
+
const std::uint8_t packed = packed_row[tail / 2];
|
| 130 |
+
const std::uint8_t index =
|
| 131 |
+
(tail & 1) == 0 ? packed & 15u : (packed >> 4) & 15u;
|
| 132 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 133 |
+
accumulator +=
|
| 134 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 135 |
+
args.centroids[index];
|
| 136 |
+
} else {
|
| 137 |
+
accumulator += static_cast<float>(
|
| 138 |
+
static_cast<scalar_t const *>(
|
| 139 |
+
args.x)[input_row_offset + tail]) *
|
| 140 |
+
args.centroids[index];
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
accumulator *= row_norm;
|
| 144 |
+
if (args.has_bias) {
|
| 145 |
+
accumulator += args.bias[out_col];
|
| 146 |
+
}
|
| 147 |
+
store_value<scalar_t>(
|
| 148 |
+
args.out,
|
| 149 |
+
(row_start + row) * args.out_features + out_col,
|
| 150 |
+
accumulator);
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
template <int Bits>
|
| 155 |
+
inline std::uint32_t unpack_index_generic(
|
| 156 |
+
std::uint8_t const *packed_row,
|
| 157 |
+
std::int64_t value_index) {
|
| 158 |
+
const std::int64_t bit_start = value_index * Bits;
|
| 159 |
+
const std::int64_t byte_index = bit_start >> 3;
|
| 160 |
+
const unsigned bit_offset = static_cast<unsigned>(bit_start & 7);
|
| 161 |
+
std::uint32_t raw = packed_row[byte_index];
|
| 162 |
+
if (bit_offset + static_cast<unsigned>(Bits) > 8) {
|
| 163 |
+
raw |= static_cast<std::uint32_t>(packed_row[byte_index + 1]) << 8;
|
| 164 |
+
}
|
| 165 |
+
return (raw >> bit_offset) & ((1u << Bits) - 1u);
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
// Each decoder turns 8 consecutive packed indices into 8 fp32 centroid
|
| 169 |
+
// values. Rows are byte-aligned because dispatch requires in_features % 4 == 0.
|
| 170 |
+
struct W2Avx2Decoder {
|
| 171 |
+
static constexpr int kBits = 2;
|
| 172 |
+
struct Tables {
|
| 173 |
+
__m256 lut;
|
| 174 |
+
};
|
| 175 |
+
|
| 176 |
+
ORBITQUANT_TARGET_AVX2 static inline Tables load_tables(
|
| 177 |
+
float const *centroids) {
|
| 178 |
+
const __m128 lut4 = _mm_loadu_ps(centroids);
|
| 179 |
+
return Tables{_mm256_set_m128(lut4, lut4)};
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
ORBITQUANT_TARGET_AVX2 static inline __m256 decode(
|
| 183 |
+
std::uint8_t const *packed_row,
|
| 184 |
+
std::int64_t k,
|
| 185 |
+
Tables const &tables) {
|
| 186 |
+
std::uint16_t packed_bits;
|
| 187 |
+
std::memcpy(&packed_bits, packed_row + (k >> 2), sizeof(packed_bits));
|
| 188 |
+
const __m128i bytes = _mm_cvtsi32_si128(packed_bits);
|
| 189 |
+
const __m128i replicated = _mm_shuffle_epi8(
|
| 190 |
+
bytes,
|
| 191 |
+
_mm_setr_epi8(0, 0, 0, 0, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1));
|
| 192 |
+
const __m256i widened = _mm256_cvtepu8_epi32(replicated);
|
| 193 |
+
const __m256i shifts = _mm256_setr_epi32(0, 2, 4, 6, 0, 2, 4, 6);
|
| 194 |
+
const __m256i indices = _mm256_and_si256(
|
| 195 |
+
_mm256_srlv_epi32(widened, shifts),
|
| 196 |
+
_mm256_set1_epi32(3));
|
| 197 |
+
return _mm256_permutevar8x32_ps(tables.lut, indices);
|
| 198 |
+
}
|
| 199 |
+
};
|
| 200 |
+
|
| 201 |
+
struct W6Avx2Decoder {
|
| 202 |
+
static constexpr int kBits = 6;
|
| 203 |
+
struct Tables {
|
| 204 |
+
float const *centroids;
|
| 205 |
+
};
|
| 206 |
+
|
| 207 |
+
ORBITQUANT_TARGET_AVX2 static inline Tables load_tables(
|
| 208 |
+
float const *centroids) {
|
| 209 |
+
return Tables{centroids};
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
ORBITQUANT_TARGET_AVX2 static inline __m256 decode(
|
| 213 |
+
std::uint8_t const *packed_row,
|
| 214 |
+
std::int64_t k,
|
| 215 |
+
Tables const &tables) {
|
| 216 |
+
std::uint64_t raw_bits = 0;
|
| 217 |
+
std::memcpy(&raw_bits, packed_row + (k * 6 >> 3), 6);
|
| 218 |
+
const __m128i raw =
|
| 219 |
+
_mm_cvtsi64_si128(static_cast<long long>(raw_bits));
|
| 220 |
+
const __m128i windows = _mm_shuffle_epi8(
|
| 221 |
+
raw,
|
| 222 |
+
_mm_setr_epi8(0, 1, 0, 1, 1, 2, 2, 3, 3, 4, 3, 4, 4, 5, 5, 6));
|
| 223 |
+
const __m256i widened = _mm256_cvtepu16_epi32(windows);
|
| 224 |
+
const __m256i shifts = _mm256_setr_epi32(0, 6, 4, 2, 0, 6, 4, 2);
|
| 225 |
+
const __m256i indices = _mm256_and_si256(
|
| 226 |
+
_mm256_srlv_epi32(widened, shifts),
|
| 227 |
+
_mm256_set1_epi32(63));
|
| 228 |
+
return _mm256_i32gather_ps(tables.centroids, indices, 4);
|
| 229 |
+
}
|
| 230 |
+
};
|
| 231 |
+
|
| 232 |
+
template <
|
| 233 |
+
typename scalar_t,
|
| 234 |
+
__m256 (*load8)(void const *, std::int64_t),
|
| 235 |
+
typename decoder_t,
|
| 236 |
+
int row_tile>
|
| 237 |
+
ORBITQUANT_TARGET_AVX2 inline void packed_matmul_avx2_lowbit_rows(
|
| 238 |
+
PackedMatmulArgs const &args,
|
| 239 |
+
std::uint8_t const *packed_row,
|
| 240 |
+
std::int64_t out_col,
|
| 241 |
+
std::int64_t row_start) {
|
| 242 |
+
__m256 accumulators[row_tile];
|
| 243 |
+
#pragma clang loop unroll(full)
|
| 244 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 245 |
+
accumulators[row] = _mm256_setzero_ps();
|
| 246 |
+
}
|
| 247 |
+
const typename decoder_t::Tables tables =
|
| 248 |
+
decoder_t::load_tables(args.centroids);
|
| 249 |
+
|
| 250 |
+
std::int64_t k = 0;
|
| 251 |
+
for (; k + 8 <= args.in_features; k += 8) {
|
| 252 |
+
const __m256 weight = decoder_t::decode(packed_row, k, tables);
|
| 253 |
+
#pragma clang loop unroll(full)
|
| 254 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 255 |
+
const std::int64_t input_offset =
|
| 256 |
+
(row_start + row) * args.in_features + k;
|
| 257 |
+
accumulators[row] = _mm256_fmadd_ps(
|
| 258 |
+
load8(args.x, input_offset),
|
| 259 |
+
weight,
|
| 260 |
+
accumulators[row]);
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
const float row_norm = args.row_norms[out_col];
|
| 265 |
+
#pragma clang loop unroll(full)
|
| 266 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 267 |
+
const std::int64_t input_row_offset =
|
| 268 |
+
(row_start + row) * args.in_features;
|
| 269 |
+
float accumulator = horizontal_sum(accumulators[row]);
|
| 270 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 271 |
+
const std::uint32_t index =
|
| 272 |
+
unpack_index_generic<decoder_t::kBits>(packed_row, tail);
|
| 273 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 274 |
+
accumulator +=
|
| 275 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 276 |
+
args.centroids[index];
|
| 277 |
+
} else {
|
| 278 |
+
accumulator += static_cast<float>(
|
| 279 |
+
static_cast<scalar_t const *>(
|
| 280 |
+
args.x)[input_row_offset + tail]) *
|
| 281 |
+
args.centroids[index];
|
| 282 |
+
}
|
| 283 |
+
}
|
| 284 |
+
accumulator *= row_norm;
|
| 285 |
+
if (args.has_bias) {
|
| 286 |
+
accumulator += args.bias[out_col];
|
| 287 |
+
}
|
| 288 |
+
store_value<scalar_t>(
|
| 289 |
+
args.out,
|
| 290 |
+
(row_start + row) * args.out_features + out_col,
|
| 291 |
+
accumulator);
|
| 292 |
+
}
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
template <typename scalar_t, __m256 (*load8)(void const *, std::int64_t), int row_tile>
|
| 296 |
+
ORBITQUANT_TARGET_AVX2 inline void packed_matmul_avx2_buffered_rows(
|
| 297 |
+
PackedMatmulArgs const &args,
|
| 298 |
+
float const *decoded_row,
|
| 299 |
+
std::int64_t out_col,
|
| 300 |
+
std::int64_t row_start) {
|
| 301 |
+
__m256 accumulators[row_tile];
|
| 302 |
+
#pragma clang loop unroll(full)
|
| 303 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 304 |
+
accumulators[row] = _mm256_setzero_ps();
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
std::int64_t k = 0;
|
| 308 |
+
for (; k + 8 <= args.in_features; k += 8) {
|
| 309 |
+
const __m256 weight = _mm256_loadu_ps(decoded_row + k);
|
| 310 |
+
#pragma clang loop unroll(full)
|
| 311 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 312 |
+
const std::int64_t input_offset =
|
| 313 |
+
(row_start + row) * args.in_features + k;
|
| 314 |
+
accumulators[row] = _mm256_fmadd_ps(
|
| 315 |
+
load8(args.x, input_offset),
|
| 316 |
+
weight,
|
| 317 |
+
accumulators[row]);
|
| 318 |
+
}
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
const float row_norm = args.row_norms[out_col];
|
| 322 |
+
#pragma clang loop unroll(full)
|
| 323 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 324 |
+
const std::int64_t input_row_offset =
|
| 325 |
+
(row_start + row) * args.in_features;
|
| 326 |
+
float accumulator = horizontal_sum(accumulators[row]);
|
| 327 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 328 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 329 |
+
accumulator +=
|
| 330 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 331 |
+
decoded_row[tail];
|
| 332 |
+
} else {
|
| 333 |
+
accumulator += static_cast<float>(
|
| 334 |
+
static_cast<scalar_t const *>(
|
| 335 |
+
args.x)[input_row_offset + tail]) *
|
| 336 |
+
decoded_row[tail];
|
| 337 |
+
}
|
| 338 |
+
}
|
| 339 |
+
accumulator *= row_norm;
|
| 340 |
+
if (args.has_bias) {
|
| 341 |
+
accumulator += args.bias[out_col];
|
| 342 |
+
}
|
| 343 |
+
store_value<scalar_t>(
|
| 344 |
+
args.out,
|
| 345 |
+
(row_start + row) * args.out_features + out_col,
|
| 346 |
+
accumulator);
|
| 347 |
+
}
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
template <
|
| 351 |
+
typename scalar_t,
|
| 352 |
+
__m256 (*load8)(void const *, std::int64_t),
|
| 353 |
+
typename decoder_t>
|
| 354 |
+
ORBITQUANT_TARGET_AVX2 ORBITQUANT_NOINLINE void
|
| 355 |
+
packed_matmul_avx2_lowbit_typed(
|
| 356 |
+
PackedMatmulArgs const &args,
|
| 357 |
+
std::int64_t out_start,
|
| 358 |
+
std::int64_t out_end) {
|
| 359 |
+
constexpr int kPrimaryRowTile = 8;
|
| 360 |
+
const std::int64_t packed_row_bytes =
|
| 361 |
+
args.in_features * decoder_t::kBits / 8;
|
| 362 |
+
// Two or more row tiles amortize the packed decode: expand the column once
|
| 363 |
+
// into a per-thread scratch row and stream plain FMA tiles from it.
|
| 364 |
+
const bool use_decoded_buffer = args.rows >= 16;
|
| 365 |
+
thread_local std::vector<float> decoded_row_storage;
|
| 366 |
+
if (use_decoded_buffer &&
|
| 367 |
+
decoded_row_storage.size() < static_cast<std::size_t>(args.in_features)) {
|
| 368 |
+
decoded_row_storage.resize(static_cast<std::size_t>(args.in_features));
|
| 369 |
+
}
|
| 370 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 371 |
+
const auto *packed_row =
|
| 372 |
+
args.packed_weight_indices + out_col * packed_row_bytes;
|
| 373 |
+
if (use_decoded_buffer) {
|
| 374 |
+
float *decoded_row = decoded_row_storage.data();
|
| 375 |
+
const typename decoder_t::Tables tables =
|
| 376 |
+
decoder_t::load_tables(args.centroids);
|
| 377 |
+
std::int64_t k = 0;
|
| 378 |
+
for (; k + 8 <= args.in_features; k += 8) {
|
| 379 |
+
_mm256_storeu_ps(decoded_row + k, decoder_t::decode(packed_row, k, tables));
|
| 380 |
+
}
|
| 381 |
+
for (; k < args.in_features; ++k) {
|
| 382 |
+
decoded_row[k] =
|
| 383 |
+
args.centroids[unpack_index_generic<decoder_t::kBits>(packed_row, k)];
|
| 384 |
+
}
|
| 385 |
+
std::int64_t row = 0;
|
| 386 |
+
for (; row + kPrimaryRowTile <= args.rows; row += kPrimaryRowTile) {
|
| 387 |
+
packed_matmul_avx2_buffered_rows<scalar_t, load8, 8>(
|
| 388 |
+
args, decoded_row, out_col, row);
|
| 389 |
+
}
|
| 390 |
+
if (row + 4 <= args.rows) {
|
| 391 |
+
packed_matmul_avx2_buffered_rows<scalar_t, load8, 4>(
|
| 392 |
+
args, decoded_row, out_col, row);
|
| 393 |
+
row += 4;
|
| 394 |
+
}
|
| 395 |
+
switch (args.rows - row) {
|
| 396 |
+
case 3:
|
| 397 |
+
packed_matmul_avx2_buffered_rows<scalar_t, load8, 3>(
|
| 398 |
+
args, decoded_row, out_col, row);
|
| 399 |
+
break;
|
| 400 |
+
case 2:
|
| 401 |
+
packed_matmul_avx2_buffered_rows<scalar_t, load8, 2>(
|
| 402 |
+
args, decoded_row, out_col, row);
|
| 403 |
+
break;
|
| 404 |
+
case 1:
|
| 405 |
+
packed_matmul_avx2_buffered_rows<scalar_t, load8, 1>(
|
| 406 |
+
args, decoded_row, out_col, row);
|
| 407 |
+
break;
|
| 408 |
+
default:
|
| 409 |
+
break;
|
| 410 |
+
}
|
| 411 |
+
continue;
|
| 412 |
+
}
|
| 413 |
+
std::int64_t row = 0;
|
| 414 |
+
for (; row + kPrimaryRowTile <= args.rows; row += kPrimaryRowTile) {
|
| 415 |
+
packed_matmul_avx2_lowbit_rows<scalar_t, load8, decoder_t, 8>(
|
| 416 |
+
args, packed_row, out_col, row);
|
| 417 |
+
}
|
| 418 |
+
if (row + 4 <= args.rows) {
|
| 419 |
+
packed_matmul_avx2_lowbit_rows<scalar_t, load8, decoder_t, 4>(
|
| 420 |
+
args, packed_row, out_col, row);
|
| 421 |
+
row += 4;
|
| 422 |
+
}
|
| 423 |
+
switch (args.rows - row) {
|
| 424 |
+
case 3:
|
| 425 |
+
packed_matmul_avx2_lowbit_rows<scalar_t, load8, decoder_t, 3>(
|
| 426 |
+
args, packed_row, out_col, row);
|
| 427 |
+
break;
|
| 428 |
+
case 2:
|
| 429 |
+
packed_matmul_avx2_lowbit_rows<scalar_t, load8, decoder_t, 2>(
|
| 430 |
+
args, packed_row, out_col, row);
|
| 431 |
+
break;
|
| 432 |
+
case 1:
|
| 433 |
+
packed_matmul_avx2_lowbit_rows<scalar_t, load8, decoder_t, 1>(
|
| 434 |
+
args, packed_row, out_col, row);
|
| 435 |
+
break;
|
| 436 |
+
default:
|
| 437 |
+
break;
|
| 438 |
+
}
|
| 439 |
+
}
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
template <typename decoder_t>
|
| 443 |
+
void packed_matmul_avx2_lowbit_dispatch(
|
| 444 |
+
PackedMatmulArgs const &args,
|
| 445 |
+
std::int64_t out_start,
|
| 446 |
+
std::int64_t out_end) {
|
| 447 |
+
switch (args.scalar_kind) {
|
| 448 |
+
case ScalarKind::Float32:
|
| 449 |
+
packed_matmul_avx2_lowbit_typed<float, load_float8, decoder_t>(
|
| 450 |
+
args, out_start, out_end);
|
| 451 |
+
return;
|
| 452 |
+
case ScalarKind::Float16:
|
| 453 |
+
packed_matmul_avx2_lowbit_typed<c10::Half, load_half8, decoder_t>(
|
| 454 |
+
args, out_start, out_end);
|
| 455 |
+
return;
|
| 456 |
+
case ScalarKind::BFloat16:
|
| 457 |
+
packed_matmul_avx2_lowbit_typed<c10::BFloat16, load_bfloat8, decoder_t>(
|
| 458 |
+
args, out_start, out_end);
|
| 459 |
+
return;
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
template <typename scalar_t>
|
| 464 |
+
bool use_verified_amd_cezanne_row_tile(PackedMatmulArgs const &args) {
|
| 465 |
+
if constexpr (!std::is_same_v<scalar_t, c10::BFloat16>) {
|
| 466 |
+
return false;
|
| 467 |
+
}
|
| 468 |
+
const bool tuned_dimension = args.in_features == 1536 ||
|
| 469 |
+
args.in_features == 1920 || args.in_features == 3072;
|
| 470 |
+
if (args.rows < 16 || !tuned_dimension) {
|
| 471 |
+
return false;
|
| 472 |
+
}
|
| 473 |
+
static const bool verified_cpu = [] {
|
| 474 |
+
unsigned int eax = 0;
|
| 475 |
+
unsigned int ebx = 0;
|
| 476 |
+
unsigned int ecx = 0;
|
| 477 |
+
unsigned int edx = 0;
|
| 478 |
+
#if defined(_MSC_VER)
|
| 479 |
+
int registers[4]{};
|
| 480 |
+
__cpuid(registers, 0);
|
| 481 |
+
eax = static_cast<unsigned int>(registers[0]);
|
| 482 |
+
ebx = static_cast<unsigned int>(registers[1]);
|
| 483 |
+
ecx = static_cast<unsigned int>(registers[2]);
|
| 484 |
+
edx = static_cast<unsigned int>(registers[3]);
|
| 485 |
+
if (ebx != 0x68747541u || edx != 0x69746e65u ||
|
| 486 |
+
ecx != 0x444d4163u) {
|
| 487 |
+
return false;
|
| 488 |
+
}
|
| 489 |
+
__cpuid(registers, 1);
|
| 490 |
+
eax = static_cast<unsigned int>(registers[0]);
|
| 491 |
+
#else
|
| 492 |
+
// CPUID vendor registers spell "AuthenticAMD" in EBX, EDX, ECX order.
|
| 493 |
+
if (!__get_cpuid(0, &eax, &ebx, &ecx, &edx) ||
|
| 494 |
+
ebx != 0x68747541u || edx != 0x69746e65u || ecx != 0x444d4163u ||
|
| 495 |
+
!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
|
| 496 |
+
return false;
|
| 497 |
+
}
|
| 498 |
+
#endif
|
| 499 |
+
const unsigned int base_family = (eax >> 8) & 0xfu;
|
| 500 |
+
const unsigned int family = base_family == 0xfu
|
| 501 |
+
? base_family + ((eax >> 20) & 0xffu)
|
| 502 |
+
: base_family;
|
| 503 |
+
// The 16-row tile was measured on Zen 3 (Ryzen 5 5600G); apply it to the
|
| 504 |
+
// whole AVX2-only Zen 3 family (19h without AVX-512) instead of pinning
|
| 505 |
+
// the one benchmarked model.
|
| 506 |
+
return family == 0x19u && !packed_matmul_x86_avx512_available();
|
| 507 |
+
}();
|
| 508 |
+
return verified_cpu;
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
template <
|
| 512 |
+
typename scalar_t,
|
| 513 |
+
__m256 (*load8)(void const *, std::int64_t),
|
| 514 |
+
int primary_row_tile>
|
| 515 |
+
ORBITQUANT_TARGET_AVX2 ORBITQUANT_NOINLINE void packed_matmul_avx2_w4_typed(
|
| 516 |
+
PackedMatmulArgs const &args,
|
| 517 |
+
std::int64_t out_start,
|
| 518 |
+
std::int64_t out_end) {
|
| 519 |
+
static_assert(primary_row_tile == 8 || primary_row_tile == 16);
|
| 520 |
+
const std::int64_t packed_row_bytes = args.in_features / 2;
|
| 521 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 522 |
+
const auto *packed_row =
|
| 523 |
+
args.packed_weight_indices + out_col * packed_row_bytes;
|
| 524 |
+
std::int64_t row = 0;
|
| 525 |
+
for (; row + primary_row_tile <= args.rows; row += primary_row_tile) {
|
| 526 |
+
packed_matmul_avx2_w4_rows<scalar_t, load8, primary_row_tile>(
|
| 527 |
+
args, packed_row, out_col, row);
|
| 528 |
+
}
|
| 529 |
+
if (row + 8 <= args.rows) {
|
| 530 |
+
packed_matmul_avx2_w4_rows<scalar_t, load8, 8>(
|
| 531 |
+
args, packed_row, out_col, row);
|
| 532 |
+
row += 8;
|
| 533 |
+
}
|
| 534 |
+
if (row + 4 <= args.rows) {
|
| 535 |
+
packed_matmul_avx2_w4_rows<scalar_t, load8, 4>(
|
| 536 |
+
args, packed_row, out_col, row);
|
| 537 |
+
row += 4;
|
| 538 |
+
}
|
| 539 |
+
switch (args.rows - row) {
|
| 540 |
+
case 3:
|
| 541 |
+
packed_matmul_avx2_w4_rows<scalar_t, load8, 3>(
|
| 542 |
+
args, packed_row, out_col, row);
|
| 543 |
+
break;
|
| 544 |
+
case 2:
|
| 545 |
+
packed_matmul_avx2_w4_rows<scalar_t, load8, 2>(
|
| 546 |
+
args, packed_row, out_col, row);
|
| 547 |
+
break;
|
| 548 |
+
case 1:
|
| 549 |
+
packed_matmul_avx2_w4_rows<scalar_t, load8, 1>(
|
| 550 |
+
args, packed_row, out_col, row);
|
| 551 |
+
break;
|
| 552 |
+
default:
|
| 553 |
+
break;
|
| 554 |
+
}
|
| 555 |
+
}
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
} // namespace
|
| 559 |
+
|
| 560 |
+
void packed_matmul_x86_avx2_range(
|
| 561 |
+
PackedMatmulArgs const &args,
|
| 562 |
+
std::int64_t out_start,
|
| 563 |
+
std::int64_t out_end) {
|
| 564 |
+
if (!packed_matmul_x86_avx2_available()) {
|
| 565 |
+
packed_matmul_scalar_range(args, out_start, out_end);
|
| 566 |
+
return;
|
| 567 |
+
}
|
| 568 |
+
if (args.bits == 2 && args.in_features % 4 == 0) {
|
| 569 |
+
packed_matmul_avx2_lowbit_dispatch<W2Avx2Decoder>(args, out_start, out_end);
|
| 570 |
+
return;
|
| 571 |
+
}
|
| 572 |
+
if (args.bits == 6 && args.in_features % 4 == 0) {
|
| 573 |
+
packed_matmul_avx2_lowbit_dispatch<W6Avx2Decoder>(args, out_start, out_end);
|
| 574 |
+
return;
|
| 575 |
+
}
|
| 576 |
+
if (args.bits != 4 || args.in_features % 2 != 0) {
|
| 577 |
+
packed_matmul_scalar_range(args, out_start, out_end);
|
| 578 |
+
return;
|
| 579 |
+
}
|
| 580 |
+
switch (args.scalar_kind) {
|
| 581 |
+
case ScalarKind::Float32:
|
| 582 |
+
packed_matmul_avx2_w4_typed<float, load_float8, 8>(
|
| 583 |
+
args, out_start, out_end);
|
| 584 |
+
return;
|
| 585 |
+
case ScalarKind::Float16:
|
| 586 |
+
packed_matmul_avx2_w4_typed<c10::Half, load_half8, 8>(
|
| 587 |
+
args, out_start, out_end);
|
| 588 |
+
return;
|
| 589 |
+
case ScalarKind::BFloat16:
|
| 590 |
+
if (use_verified_amd_cezanne_row_tile<c10::BFloat16>(args)) {
|
| 591 |
+
packed_matmul_avx2_w4_typed<c10::BFloat16, load_bfloat8, 16>(
|
| 592 |
+
args, out_start, out_end);
|
| 593 |
+
} else {
|
| 594 |
+
packed_matmul_avx2_w4_typed<c10::BFloat16, load_bfloat8, 8>(
|
| 595 |
+
args, out_start, out_end);
|
| 596 |
+
}
|
| 597 |
+
return;
|
| 598 |
+
}
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
} // namespace orbitquant::cpu
|
| 602 |
+
|
| 603 |
+
#else
|
| 604 |
+
|
| 605 |
+
namespace orbitquant::cpu {
|
| 606 |
+
|
| 607 |
+
void packed_matmul_x86_avx2_range(
|
| 608 |
+
PackedMatmulArgs const &args,
|
| 609 |
+
std::int64_t out_start,
|
| 610 |
+
std::int64_t out_end) {
|
| 611 |
+
packed_matmul_scalar_range(args, out_start, out_end);
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
} // namespace orbitquant::cpu
|
| 615 |
+
|
| 616 |
+
#endif
|
orbitquant_packed_matmul_cpu/packed_matmul_x86_avx512.cpp
ADDED
|
@@ -0,0 +1,851 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "packed_matmul_cpu.h"
|
| 2 |
+
|
| 3 |
+
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER)
|
| 4 |
+
#include <cpuid.h>
|
| 5 |
+
#include <immintrin.h>
|
| 6 |
+
|
| 7 |
+
#include <torch/headeronly/util/BFloat16.h>
|
| 8 |
+
#include <torch/headeronly/util/Half.h>
|
| 9 |
+
|
| 10 |
+
#include <cstring>
|
| 11 |
+
#include <cstdint>
|
| 12 |
+
#include <type_traits>
|
| 13 |
+
#include <vector>
|
| 14 |
+
|
| 15 |
+
#define ORBITQUANT_TARGET_AVX512 \
|
| 16 |
+
__attribute__((target("avx512f,avx512dq,avx512bw,avx512vl,fma,f16c")))
|
| 17 |
+
#define ORBITQUANT_TARGET_AVX512_BF16 \
|
| 18 |
+
__attribute__((target( \
|
| 19 |
+
"avx512f,avx512dq,avx512bw,avx512vl,avx512bf16,fma,f16c")))
|
| 20 |
+
#define ORBITQUANT_HAS_AVX512_BF16_INTRINSICS 1
|
| 21 |
+
#define ORBITQUANT_NOINLINE __attribute__((noinline))
|
| 22 |
+
#define ORBITQUANT_ALWAYS_INLINE __attribute__((always_inline))
|
| 23 |
+
|
| 24 |
+
namespace orbitquant::cpu {
|
| 25 |
+
namespace {
|
| 26 |
+
|
| 27 |
+
ORBITQUANT_TARGET_AVX512 inline float horizontal_sum(__m512 value) {
|
| 28 |
+
return _mm512_reduce_add_ps(value);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
ORBITQUANT_TARGET_AVX512 inline __m512 load_float16(
|
| 32 |
+
void const *data,
|
| 33 |
+
std::int64_t offset) {
|
| 34 |
+
return _mm512_loadu_ps(static_cast<float const *>(data) + offset);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
ORBITQUANT_TARGET_AVX512 inline __m512 load_half16(
|
| 38 |
+
void const *data,
|
| 39 |
+
std::int64_t offset) {
|
| 40 |
+
const auto *source = static_cast<std::uint16_t const *>(data) + offset;
|
| 41 |
+
const __m256i packed =
|
| 42 |
+
_mm256_loadu_si256(reinterpret_cast<__m256i const *>(source));
|
| 43 |
+
return _mm512_cvtph_ps(packed);
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
ORBITQUANT_TARGET_AVX512 inline __m512 load_bfloat16(
|
| 47 |
+
void const *data,
|
| 48 |
+
std::int64_t offset) {
|
| 49 |
+
const auto *source = static_cast<std::uint16_t const *>(data) + offset;
|
| 50 |
+
const __m256i packed =
|
| 51 |
+
_mm256_loadu_si256(reinterpret_cast<__m256i const *>(source));
|
| 52 |
+
const __m512i widened = _mm512_cvtepu16_epi32(packed);
|
| 53 |
+
return _mm512_castsi512_ps(_mm512_slli_epi32(widened, 16));
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
template <typename scalar_t>
|
| 57 |
+
inline void store_value(void *data, std::int64_t offset, float value) {
|
| 58 |
+
static_cast<scalar_t *>(data)[offset] = scalar_t(value);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
template <>
|
| 62 |
+
inline void store_value<float>(void *data, std::int64_t offset, float value) {
|
| 63 |
+
static_cast<float *>(data)[offset] = value;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
template <
|
| 67 |
+
typename scalar_t,
|
| 68 |
+
__m512 (*load16)(void const *, std::int64_t),
|
| 69 |
+
int row_tile>
|
| 70 |
+
ORBITQUANT_TARGET_AVX512 inline void packed_matmul_avx512_w4_rows(
|
| 71 |
+
PackedMatmulArgs const &args,
|
| 72 |
+
std::uint8_t const *packed_row,
|
| 73 |
+
std::int64_t out_col,
|
| 74 |
+
std::int64_t row_start) {
|
| 75 |
+
__m512 accumulators[row_tile];
|
| 76 |
+
#pragma clang loop unroll(full)
|
| 77 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 78 |
+
accumulators[row] = _mm512_setzero_ps();
|
| 79 |
+
}
|
| 80 |
+
const __m512 centroid_lut = _mm512_loadu_ps(args.centroids);
|
| 81 |
+
const __m128i nibble_mask = _mm_set1_epi8(15);
|
| 82 |
+
|
| 83 |
+
std::int64_t k = 0;
|
| 84 |
+
for (; k + 16 <= args.in_features; k += 16) {
|
| 85 |
+
const std::int64_t byte_offset = k / 2;
|
| 86 |
+
std::int64_t packed;
|
| 87 |
+
std::memcpy(&packed, packed_row + byte_offset, sizeof(packed));
|
| 88 |
+
const __m128i bytes = _mm_cvtsi64_si128(packed);
|
| 89 |
+
const __m128i low = _mm_and_si128(bytes, nibble_mask);
|
| 90 |
+
const __m128i high = _mm_and_si128(
|
| 91 |
+
_mm_srli_epi16(bytes, 4),
|
| 92 |
+
nibble_mask);
|
| 93 |
+
const __m512i indices =
|
| 94 |
+
_mm512_cvtepu8_epi32(_mm_unpacklo_epi8(low, high));
|
| 95 |
+
const __m512 weight = _mm512_permutexvar_ps(indices, centroid_lut);
|
| 96 |
+
#pragma clang loop unroll(full)
|
| 97 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 98 |
+
const std::int64_t input_offset =
|
| 99 |
+
(row_start + row) * args.in_features + k;
|
| 100 |
+
accumulators[row] = _mm512_fmadd_ps(
|
| 101 |
+
load16(args.x, input_offset),
|
| 102 |
+
weight,
|
| 103 |
+
accumulators[row]);
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
const float row_norm = args.row_norms[out_col];
|
| 108 |
+
#pragma clang loop unroll(full)
|
| 109 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 110 |
+
const std::int64_t input_row_offset =
|
| 111 |
+
(row_start + row) * args.in_features;
|
| 112 |
+
float accumulator = horizontal_sum(accumulators[row]);
|
| 113 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 114 |
+
const std::uint8_t packed = packed_row[tail / 2];
|
| 115 |
+
const std::uint8_t index =
|
| 116 |
+
(tail & 1) == 0 ? packed & 15u : (packed >> 4) & 15u;
|
| 117 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 118 |
+
accumulator +=
|
| 119 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 120 |
+
args.centroids[index];
|
| 121 |
+
} else {
|
| 122 |
+
accumulator += static_cast<float>(
|
| 123 |
+
static_cast<scalar_t const *>(
|
| 124 |
+
args.x)[input_row_offset + tail]) *
|
| 125 |
+
args.centroids[index];
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
accumulator *= row_norm;
|
| 129 |
+
if (args.has_bias) {
|
| 130 |
+
accumulator += args.bias[out_col];
|
| 131 |
+
}
|
| 132 |
+
store_value<scalar_t>(
|
| 133 |
+
args.out,
|
| 134 |
+
(row_start + row) * args.out_features + out_col,
|
| 135 |
+
accumulator);
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
template <typename scalar_t, __m512 (*load16)(void const *, std::int64_t)>
|
| 140 |
+
ORBITQUANT_TARGET_AVX512 ORBITQUANT_NOINLINE void packed_matmul_avx512_w4_typed(
|
| 141 |
+
PackedMatmulArgs const &args,
|
| 142 |
+
std::int64_t out_start,
|
| 143 |
+
std::int64_t out_end) {
|
| 144 |
+
constexpr int kPrimaryRowTile = 8;
|
| 145 |
+
const std::int64_t packed_row_bytes = args.in_features / 2;
|
| 146 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 147 |
+
const auto *packed_row =
|
| 148 |
+
args.packed_weight_indices + out_col * packed_row_bytes;
|
| 149 |
+
std::int64_t row = 0;
|
| 150 |
+
for (; row + kPrimaryRowTile <= args.rows; row += kPrimaryRowTile) {
|
| 151 |
+
packed_matmul_avx512_w4_rows<scalar_t, load16, kPrimaryRowTile>(
|
| 152 |
+
args, packed_row, out_col, row);
|
| 153 |
+
}
|
| 154 |
+
if (row + 8 <= args.rows) {
|
| 155 |
+
packed_matmul_avx512_w4_rows<scalar_t, load16, 8>(
|
| 156 |
+
args, packed_row, out_col, row);
|
| 157 |
+
row += 8;
|
| 158 |
+
}
|
| 159 |
+
if (row + 4 <= args.rows) {
|
| 160 |
+
packed_matmul_avx512_w4_rows<scalar_t, load16, 4>(
|
| 161 |
+
args, packed_row, out_col, row);
|
| 162 |
+
row += 4;
|
| 163 |
+
}
|
| 164 |
+
switch (args.rows - row) {
|
| 165 |
+
case 3:
|
| 166 |
+
packed_matmul_avx512_w4_rows<scalar_t, load16, 3>(
|
| 167 |
+
args, packed_row, out_col, row);
|
| 168 |
+
break;
|
| 169 |
+
case 2:
|
| 170 |
+
packed_matmul_avx512_w4_rows<scalar_t, load16, 2>(
|
| 171 |
+
args, packed_row, out_col, row);
|
| 172 |
+
break;
|
| 173 |
+
case 1:
|
| 174 |
+
packed_matmul_avx512_w4_rows<scalar_t, load16, 1>(
|
| 175 |
+
args, packed_row, out_col, row);
|
| 176 |
+
break;
|
| 177 |
+
default:
|
| 178 |
+
break;
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
template <int Bits>
|
| 184 |
+
inline std::uint32_t unpack_index_generic(
|
| 185 |
+
std::uint8_t const *packed_row,
|
| 186 |
+
std::int64_t value_index) {
|
| 187 |
+
const std::int64_t bit_start = value_index * Bits;
|
| 188 |
+
const std::int64_t byte_index = bit_start >> 3;
|
| 189 |
+
const unsigned bit_offset = static_cast<unsigned>(bit_start & 7);
|
| 190 |
+
std::uint32_t raw = packed_row[byte_index];
|
| 191 |
+
if (bit_offset + static_cast<unsigned>(Bits) > 8) {
|
| 192 |
+
raw |= static_cast<std::uint32_t>(packed_row[byte_index + 1]) << 8;
|
| 193 |
+
}
|
| 194 |
+
return (raw >> bit_offset) & ((1u << Bits) - 1u);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
// Each decoder turns 16 consecutive packed indices into 16 fp32 centroid
|
| 198 |
+
// values. Rows are byte-aligned because dispatch requires in_features
|
| 199 |
+
// divisibility (W2/W6: in % 4 == 0, W3: in % 8 == 0).
|
| 200 |
+
struct W2Avx512Decoder {
|
| 201 |
+
static constexpr int kBits = 2;
|
| 202 |
+
struct Tables {
|
| 203 |
+
__m512 lut;
|
| 204 |
+
};
|
| 205 |
+
|
| 206 |
+
ORBITQUANT_TARGET_AVX512 static inline Tables load_tables(
|
| 207 |
+
float const *centroids) {
|
| 208 |
+
return Tables{_mm512_broadcast_f32x4(_mm_loadu_ps(centroids))};
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
ORBITQUANT_TARGET_AVX512 static inline __m512 decode(
|
| 212 |
+
std::uint8_t const *packed_row,
|
| 213 |
+
std::int64_t k,
|
| 214 |
+
Tables const &tables) {
|
| 215 |
+
std::uint32_t packed_bits;
|
| 216 |
+
std::memcpy(&packed_bits, packed_row + (k >> 2), sizeof(packed_bits));
|
| 217 |
+
const __m128i bytes = _mm_cvtsi32_si128(static_cast<int>(packed_bits));
|
| 218 |
+
const __m128i replicated = _mm_shuffle_epi8(
|
| 219 |
+
bytes,
|
| 220 |
+
_mm_setr_epi8(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3));
|
| 221 |
+
const __m512i widened = _mm512_cvtepu8_epi32(replicated);
|
| 222 |
+
const __m512i shifts = _mm512_set_epi32(
|
| 223 |
+
6, 4, 2, 0, 6, 4, 2, 0, 6, 4, 2, 0, 6, 4, 2, 0);
|
| 224 |
+
const __m512i indices = _mm512_and_si512(
|
| 225 |
+
_mm512_srlv_epi32(widened, shifts),
|
| 226 |
+
_mm512_set1_epi32(3));
|
| 227 |
+
return _mm512_permutexvar_ps(indices, tables.lut);
|
| 228 |
+
}
|
| 229 |
+
};
|
| 230 |
+
|
| 231 |
+
struct W3Avx512Decoder {
|
| 232 |
+
static constexpr int kBits = 3;
|
| 233 |
+
struct Tables {
|
| 234 |
+
__m512 lut;
|
| 235 |
+
};
|
| 236 |
+
|
| 237 |
+
ORBITQUANT_TARGET_AVX512 static inline Tables load_tables(
|
| 238 |
+
float const *centroids) {
|
| 239 |
+
return Tables{_mm512_broadcast_f32x8(_mm256_loadu_ps(centroids))};
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
ORBITQUANT_TARGET_AVX512 static inline __m512 decode(
|
| 243 |
+
std::uint8_t const *packed_row,
|
| 244 |
+
std::int64_t k,
|
| 245 |
+
Tables const &tables) {
|
| 246 |
+
std::uint64_t raw_bits = 0;
|
| 247 |
+
std::memcpy(&raw_bits, packed_row + (k * 3 >> 3), 6);
|
| 248 |
+
const __m128i raw = _mm_cvtsi64_si128(static_cast<long long>(raw_bits));
|
| 249 |
+
const __m128i window_low = _mm_shuffle_epi8(
|
| 250 |
+
raw,
|
| 251 |
+
_mm_setr_epi8(0, 1, 0, 1, 0, 1, 1, 2, 1, 2, 1, 2, 2, 3, 2, 3));
|
| 252 |
+
const __m128i window_high = _mm_shuffle_epi8(
|
| 253 |
+
raw,
|
| 254 |
+
_mm_setr_epi8(3, 4, 3, 4, 3, 4, 4, 5, 4, 5, 4, 5, 5, 6, 5, 6));
|
| 255 |
+
const __m512i widened = _mm512_cvtepu16_epi32(
|
| 256 |
+
_mm256_set_m128i(window_high, window_low));
|
| 257 |
+
const __m512i shifts = _mm512_set_epi32(
|
| 258 |
+
5, 2, 7, 4, 1, 6, 3, 0, 5, 2, 7, 4, 1, 6, 3, 0);
|
| 259 |
+
const __m512i indices = _mm512_and_si512(
|
| 260 |
+
_mm512_srlv_epi32(widened, shifts),
|
| 261 |
+
_mm512_set1_epi32(7));
|
| 262 |
+
return _mm512_permutexvar_ps(indices, tables.lut);
|
| 263 |
+
}
|
| 264 |
+
};
|
| 265 |
+
|
| 266 |
+
struct W6Avx512Decoder {
|
| 267 |
+
static constexpr int kBits = 6;
|
| 268 |
+
struct Tables {
|
| 269 |
+
__m512 lut0;
|
| 270 |
+
__m512 lut1;
|
| 271 |
+
__m512 lut2;
|
| 272 |
+
__m512 lut3;
|
| 273 |
+
};
|
| 274 |
+
|
| 275 |
+
ORBITQUANT_TARGET_AVX512 static inline Tables load_tables(
|
| 276 |
+
float const *centroids) {
|
| 277 |
+
return Tables{
|
| 278 |
+
_mm512_loadu_ps(centroids),
|
| 279 |
+
_mm512_loadu_ps(centroids + 16),
|
| 280 |
+
_mm512_loadu_ps(centroids + 32),
|
| 281 |
+
_mm512_loadu_ps(centroids + 48)};
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
ORBITQUANT_TARGET_AVX512 static inline __m512 decode(
|
| 285 |
+
std::uint8_t const *packed_row,
|
| 286 |
+
std::int64_t k,
|
| 287 |
+
Tables const &tables) {
|
| 288 |
+
std::uint64_t low_bytes;
|
| 289 |
+
std::uint32_t high_bytes;
|
| 290 |
+
std::uint8_t const *source = packed_row + (k * 6 >> 3);
|
| 291 |
+
std::memcpy(&low_bytes, source, sizeof(low_bytes));
|
| 292 |
+
std::memcpy(&high_bytes, source + 8, sizeof(high_bytes));
|
| 293 |
+
const __m128i raw = _mm_set_epi64x(
|
| 294 |
+
static_cast<long long>(high_bytes),
|
| 295 |
+
static_cast<long long>(low_bytes));
|
| 296 |
+
const __m128i window_low = _mm_shuffle_epi8(
|
| 297 |
+
raw,
|
| 298 |
+
_mm_setr_epi8(0, 1, 0, 1, 1, 2, 2, 3, 3, 4, 3, 4, 4, 5, 5, 6));
|
| 299 |
+
const __m128i window_high = _mm_shuffle_epi8(
|
| 300 |
+
raw,
|
| 301 |
+
_mm_setr_epi8(6, 7, 6, 7, 7, 8, 8, 9, 9, 10, 9, 10, 10, 11, 11, 12));
|
| 302 |
+
const __m512i widened = _mm512_cvtepu16_epi32(
|
| 303 |
+
_mm256_set_m128i(window_high, window_low));
|
| 304 |
+
const __m512i shifts = _mm512_set_epi32(
|
| 305 |
+
2, 4, 6, 0, 2, 4, 6, 0, 2, 4, 6, 0, 2, 4, 6, 0);
|
| 306 |
+
const __m512i indices = _mm512_and_si512(
|
| 307 |
+
_mm512_srlv_epi32(widened, shifts),
|
| 308 |
+
_mm512_set1_epi32(63));
|
| 309 |
+
const __m512 low_pair = _mm512_permutex2var_ps(tables.lut0, indices, tables.lut1);
|
| 310 |
+
const __m512 high_pair = _mm512_permutex2var_ps(tables.lut2, indices, tables.lut3);
|
| 311 |
+
const __mmask16 use_high =
|
| 312 |
+
_mm512_test_epi32_mask(indices, _mm512_set1_epi32(32));
|
| 313 |
+
return _mm512_mask_blend_ps(use_high, low_pair, high_pair);
|
| 314 |
+
}
|
| 315 |
+
};
|
| 316 |
+
|
| 317 |
+
template <
|
| 318 |
+
typename scalar_t,
|
| 319 |
+
__m512 (*load16)(void const *, std::int64_t),
|
| 320 |
+
typename decoder_t,
|
| 321 |
+
int row_tile>
|
| 322 |
+
ORBITQUANT_TARGET_AVX512 inline void packed_matmul_avx512_lowbit_rows(
|
| 323 |
+
PackedMatmulArgs const &args,
|
| 324 |
+
std::uint8_t const *packed_row,
|
| 325 |
+
std::int64_t out_col,
|
| 326 |
+
std::int64_t row_start) {
|
| 327 |
+
__m512 accumulators[row_tile];
|
| 328 |
+
#pragma clang loop unroll(full)
|
| 329 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 330 |
+
accumulators[row] = _mm512_setzero_ps();
|
| 331 |
+
}
|
| 332 |
+
const typename decoder_t::Tables tables =
|
| 333 |
+
decoder_t::load_tables(args.centroids);
|
| 334 |
+
|
| 335 |
+
std::int64_t k = 0;
|
| 336 |
+
for (; k + 16 <= args.in_features; k += 16) {
|
| 337 |
+
const __m512 weight = decoder_t::decode(packed_row, k, tables);
|
| 338 |
+
#pragma clang loop unroll(full)
|
| 339 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 340 |
+
const std::int64_t input_offset =
|
| 341 |
+
(row_start + row) * args.in_features + k;
|
| 342 |
+
accumulators[row] = _mm512_fmadd_ps(
|
| 343 |
+
load16(args.x, input_offset),
|
| 344 |
+
weight,
|
| 345 |
+
accumulators[row]);
|
| 346 |
+
}
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
const float row_norm = args.row_norms[out_col];
|
| 350 |
+
#pragma clang loop unroll(full)
|
| 351 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 352 |
+
const std::int64_t input_row_offset =
|
| 353 |
+
(row_start + row) * args.in_features;
|
| 354 |
+
float accumulator = horizontal_sum(accumulators[row]);
|
| 355 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 356 |
+
const std::uint32_t index =
|
| 357 |
+
unpack_index_generic<decoder_t::kBits>(packed_row, tail);
|
| 358 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 359 |
+
accumulator +=
|
| 360 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 361 |
+
args.centroids[index];
|
| 362 |
+
} else {
|
| 363 |
+
accumulator += static_cast<float>(
|
| 364 |
+
static_cast<scalar_t const *>(
|
| 365 |
+
args.x)[input_row_offset + tail]) *
|
| 366 |
+
args.centroids[index];
|
| 367 |
+
}
|
| 368 |
+
}
|
| 369 |
+
accumulator *= row_norm;
|
| 370 |
+
if (args.has_bias) {
|
| 371 |
+
accumulator += args.bias[out_col];
|
| 372 |
+
}
|
| 373 |
+
store_value<scalar_t>(
|
| 374 |
+
args.out,
|
| 375 |
+
(row_start + row) * args.out_features + out_col,
|
| 376 |
+
accumulator);
|
| 377 |
+
}
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
template <typename scalar_t, __m512 (*load16)(void const *, std::int64_t), int row_tile>
|
| 381 |
+
ORBITQUANT_TARGET_AVX512 inline void packed_matmul_avx512_buffered_rows(
|
| 382 |
+
PackedMatmulArgs const &args,
|
| 383 |
+
float const *decoded_row,
|
| 384 |
+
std::int64_t out_col,
|
| 385 |
+
std::int64_t row_start) {
|
| 386 |
+
__m512 accumulators[row_tile];
|
| 387 |
+
#pragma clang loop unroll(full)
|
| 388 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 389 |
+
accumulators[row] = _mm512_setzero_ps();
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
std::int64_t k = 0;
|
| 393 |
+
for (; k + 16 <= args.in_features; k += 16) {
|
| 394 |
+
const __m512 weight = _mm512_loadu_ps(decoded_row + k);
|
| 395 |
+
#pragma clang loop unroll(full)
|
| 396 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 397 |
+
const std::int64_t input_offset =
|
| 398 |
+
(row_start + row) * args.in_features + k;
|
| 399 |
+
accumulators[row] = _mm512_fmadd_ps(
|
| 400 |
+
load16(args.x, input_offset),
|
| 401 |
+
weight,
|
| 402 |
+
accumulators[row]);
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
const float row_norm = args.row_norms[out_col];
|
| 407 |
+
#pragma clang loop unroll(full)
|
| 408 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 409 |
+
const std::int64_t input_row_offset =
|
| 410 |
+
(row_start + row) * args.in_features;
|
| 411 |
+
float accumulator = horizontal_sum(accumulators[row]);
|
| 412 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 413 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 414 |
+
accumulator +=
|
| 415 |
+
static_cast<float const *>(args.x)[input_row_offset + tail] *
|
| 416 |
+
decoded_row[tail];
|
| 417 |
+
} else {
|
| 418 |
+
accumulator += static_cast<float>(
|
| 419 |
+
static_cast<scalar_t const *>(
|
| 420 |
+
args.x)[input_row_offset + tail]) *
|
| 421 |
+
decoded_row[tail];
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
+
accumulator *= row_norm;
|
| 425 |
+
if (args.has_bias) {
|
| 426 |
+
accumulator += args.bias[out_col];
|
| 427 |
+
}
|
| 428 |
+
store_value<scalar_t>(
|
| 429 |
+
args.out,
|
| 430 |
+
(row_start + row) * args.out_features + out_col,
|
| 431 |
+
accumulator);
|
| 432 |
+
}
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
template <
|
| 436 |
+
typename scalar_t,
|
| 437 |
+
__m512 (*load16)(void const *, std::int64_t),
|
| 438 |
+
typename decoder_t>
|
| 439 |
+
ORBITQUANT_TARGET_AVX512 ORBITQUANT_NOINLINE void
|
| 440 |
+
packed_matmul_avx512_lowbit_typed(
|
| 441 |
+
PackedMatmulArgs const &args,
|
| 442 |
+
std::int64_t out_start,
|
| 443 |
+
std::int64_t out_end) {
|
| 444 |
+
constexpr int kPrimaryRowTile = 8;
|
| 445 |
+
const std::int64_t packed_row_bytes =
|
| 446 |
+
args.in_features * decoder_t::kBits / 8;
|
| 447 |
+
// Two or more row tiles amortize the packed decode: expand the column once
|
| 448 |
+
// into a per-thread scratch row and stream plain FMA tiles from it.
|
| 449 |
+
const bool use_decoded_buffer = args.rows >= 16;
|
| 450 |
+
thread_local std::vector<float> decoded_row_storage;
|
| 451 |
+
if (use_decoded_buffer &&
|
| 452 |
+
decoded_row_storage.size() < static_cast<std::size_t>(args.in_features)) {
|
| 453 |
+
decoded_row_storage.resize(static_cast<std::size_t>(args.in_features));
|
| 454 |
+
}
|
| 455 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 456 |
+
const auto *packed_row =
|
| 457 |
+
args.packed_weight_indices + out_col * packed_row_bytes;
|
| 458 |
+
if (use_decoded_buffer) {
|
| 459 |
+
float *decoded_row = decoded_row_storage.data();
|
| 460 |
+
const typename decoder_t::Tables tables =
|
| 461 |
+
decoder_t::load_tables(args.centroids);
|
| 462 |
+
std::int64_t k = 0;
|
| 463 |
+
for (; k + 16 <= args.in_features; k += 16) {
|
| 464 |
+
_mm512_storeu_ps(decoded_row + k, decoder_t::decode(packed_row, k, tables));
|
| 465 |
+
}
|
| 466 |
+
for (; k < args.in_features; ++k) {
|
| 467 |
+
decoded_row[k] =
|
| 468 |
+
args.centroids[unpack_index_generic<decoder_t::kBits>(packed_row, k)];
|
| 469 |
+
}
|
| 470 |
+
std::int64_t row = 0;
|
| 471 |
+
for (; row + kPrimaryRowTile <= args.rows; row += kPrimaryRowTile) {
|
| 472 |
+
packed_matmul_avx512_buffered_rows<scalar_t, load16, 8>(
|
| 473 |
+
args, decoded_row, out_col, row);
|
| 474 |
+
}
|
| 475 |
+
if (row + 4 <= args.rows) {
|
| 476 |
+
packed_matmul_avx512_buffered_rows<scalar_t, load16, 4>(
|
| 477 |
+
args, decoded_row, out_col, row);
|
| 478 |
+
row += 4;
|
| 479 |
+
}
|
| 480 |
+
switch (args.rows - row) {
|
| 481 |
+
case 3:
|
| 482 |
+
packed_matmul_avx512_buffered_rows<scalar_t, load16, 3>(
|
| 483 |
+
args, decoded_row, out_col, row);
|
| 484 |
+
break;
|
| 485 |
+
case 2:
|
| 486 |
+
packed_matmul_avx512_buffered_rows<scalar_t, load16, 2>(
|
| 487 |
+
args, decoded_row, out_col, row);
|
| 488 |
+
break;
|
| 489 |
+
case 1:
|
| 490 |
+
packed_matmul_avx512_buffered_rows<scalar_t, load16, 1>(
|
| 491 |
+
args, decoded_row, out_col, row);
|
| 492 |
+
break;
|
| 493 |
+
default:
|
| 494 |
+
break;
|
| 495 |
+
}
|
| 496 |
+
continue;
|
| 497 |
+
}
|
| 498 |
+
std::int64_t row = 0;
|
| 499 |
+
for (; row + kPrimaryRowTile <= args.rows; row += kPrimaryRowTile) {
|
| 500 |
+
packed_matmul_avx512_lowbit_rows<scalar_t, load16, decoder_t, 8>(
|
| 501 |
+
args, packed_row, out_col, row);
|
| 502 |
+
}
|
| 503 |
+
if (row + 4 <= args.rows) {
|
| 504 |
+
packed_matmul_avx512_lowbit_rows<scalar_t, load16, decoder_t, 4>(
|
| 505 |
+
args, packed_row, out_col, row);
|
| 506 |
+
row += 4;
|
| 507 |
+
}
|
| 508 |
+
switch (args.rows - row) {
|
| 509 |
+
case 3:
|
| 510 |
+
packed_matmul_avx512_lowbit_rows<scalar_t, load16, decoder_t, 3>(
|
| 511 |
+
args, packed_row, out_col, row);
|
| 512 |
+
break;
|
| 513 |
+
case 2:
|
| 514 |
+
packed_matmul_avx512_lowbit_rows<scalar_t, load16, decoder_t, 2>(
|
| 515 |
+
args, packed_row, out_col, row);
|
| 516 |
+
break;
|
| 517 |
+
case 1:
|
| 518 |
+
packed_matmul_avx512_lowbit_rows<scalar_t, load16, decoder_t, 1>(
|
| 519 |
+
args, packed_row, out_col, row);
|
| 520 |
+
break;
|
| 521 |
+
default:
|
| 522 |
+
break;
|
| 523 |
+
}
|
| 524 |
+
}
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
template <typename decoder_t>
|
| 528 |
+
ORBITQUANT_TARGET_AVX512 void packed_matmul_avx512_lowbit_dispatch(
|
| 529 |
+
PackedMatmulArgs const &args,
|
| 530 |
+
std::int64_t out_start,
|
| 531 |
+
std::int64_t out_end) {
|
| 532 |
+
switch (args.scalar_kind) {
|
| 533 |
+
case ScalarKind::Float32:
|
| 534 |
+
packed_matmul_avx512_lowbit_typed<float, load_float16, decoder_t>(
|
| 535 |
+
args, out_start, out_end);
|
| 536 |
+
return;
|
| 537 |
+
case ScalarKind::Float16:
|
| 538 |
+
packed_matmul_avx512_lowbit_typed<c10::Half, load_half16, decoder_t>(
|
| 539 |
+
args, out_start, out_end);
|
| 540 |
+
return;
|
| 541 |
+
case ScalarKind::BFloat16:
|
| 542 |
+
packed_matmul_avx512_lowbit_typed<c10::BFloat16, load_bfloat16, decoder_t>(
|
| 543 |
+
args, out_start, out_end);
|
| 544 |
+
return;
|
| 545 |
+
}
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
#if defined(ORBITQUANT_HAS_AVX512_BF16_INTRINSICS)
|
| 549 |
+
template <int row_tile>
|
| 550 |
+
ORBITQUANT_TARGET_AVX512_BF16 ORBITQUANT_ALWAYS_INLINE inline void
|
| 551 |
+
accumulate_bf16_w4_chunk(
|
| 552 |
+
PackedMatmulArgs const &args,
|
| 553 |
+
std::uint8_t const *packed_row,
|
| 554 |
+
std::int64_t row_start,
|
| 555 |
+
std::int64_t k,
|
| 556 |
+
__m512i centroid_lut_words,
|
| 557 |
+
__m128i nibble_mask,
|
| 558 |
+
__m512 (&accumulators)[row_tile]) {
|
| 559 |
+
const std::int64_t byte_offset = k / 2;
|
| 560 |
+
const __m128i bytes = _mm_loadu_si128(
|
| 561 |
+
reinterpret_cast<__m128i const *>(packed_row + byte_offset));
|
| 562 |
+
const __m128i low = _mm_and_si128(bytes, nibble_mask);
|
| 563 |
+
const __m128i high = _mm_and_si128(
|
| 564 |
+
_mm_srli_epi16(bytes, 4),
|
| 565 |
+
nibble_mask);
|
| 566 |
+
const __m256i packed_indices = _mm256_set_m128i(
|
| 567 |
+
_mm_unpackhi_epi8(low, high),
|
| 568 |
+
_mm_unpacklo_epi8(low, high));
|
| 569 |
+
const __m512i indices = _mm512_cvtepu8_epi16(packed_indices);
|
| 570 |
+
const __m512bh weights = (__m512bh)_mm512_permutexvar_epi16(
|
| 571 |
+
indices,
|
| 572 |
+
centroid_lut_words);
|
| 573 |
+
#pragma clang loop unroll(full)
|
| 574 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 575 |
+
const std::int64_t input_offset =
|
| 576 |
+
(row_start + row) * args.in_features + k;
|
| 577 |
+
const __m512bh activations = (__m512bh)_mm512_loadu_si512(
|
| 578 |
+
static_cast<std::uint16_t const *>(args.x) + input_offset);
|
| 579 |
+
accumulators[row] =
|
| 580 |
+
_mm512_dpbf16_ps(accumulators[row], activations, weights);
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
template <int row_tile, bool aligned_k, bool unroll_k>
|
| 585 |
+
ORBITQUANT_TARGET_AVX512_BF16 inline void packed_matmul_avx512_bf16_w4_rows(
|
| 586 |
+
PackedMatmulArgs const &args,
|
| 587 |
+
std::uint8_t const *packed_row,
|
| 588 |
+
std::int64_t out_col,
|
| 589 |
+
std::int64_t row_start) {
|
| 590 |
+
__m512 accumulators[row_tile];
|
| 591 |
+
#pragma clang loop unroll(full)
|
| 592 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 593 |
+
accumulators[row] = _mm512_setzero_ps();
|
| 594 |
+
}
|
| 595 |
+
const float row_norm = args.row_norms[out_col];
|
| 596 |
+
const __m512 centroid_lut = _mm512_mul_ps(
|
| 597 |
+
_mm512_loadu_ps(args.centroids),
|
| 598 |
+
_mm512_set1_ps(row_norm));
|
| 599 |
+
const __m512i centroid_lut_words = (__m512i)_mm512_cvtne2ps_pbh(
|
| 600 |
+
centroid_lut,
|
| 601 |
+
centroid_lut);
|
| 602 |
+
const __m128i nibble_mask = _mm_set1_epi8(15);
|
| 603 |
+
|
| 604 |
+
std::int64_t k = 0;
|
| 605 |
+
if constexpr (unroll_k) {
|
| 606 |
+
#if defined(__clang__)
|
| 607 |
+
#pragma clang loop unroll_count(2)
|
| 608 |
+
#elif defined(__GNUC__)
|
| 609 |
+
#pragma GCC unroll 2
|
| 610 |
+
#endif
|
| 611 |
+
for (; k + 32 <= args.in_features; k += 32) {
|
| 612 |
+
accumulate_bf16_w4_chunk<row_tile>(
|
| 613 |
+
args,
|
| 614 |
+
packed_row,
|
| 615 |
+
row_start,
|
| 616 |
+
k,
|
| 617 |
+
centroid_lut_words,
|
| 618 |
+
nibble_mask,
|
| 619 |
+
accumulators);
|
| 620 |
+
}
|
| 621 |
+
} else {
|
| 622 |
+
for (; k + 32 <= args.in_features; k += 32) {
|
| 623 |
+
accumulate_bf16_w4_chunk<row_tile>(
|
| 624 |
+
args,
|
| 625 |
+
packed_row,
|
| 626 |
+
row_start,
|
| 627 |
+
k,
|
| 628 |
+
centroid_lut_words,
|
| 629 |
+
nibble_mask,
|
| 630 |
+
accumulators);
|
| 631 |
+
}
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
#pragma clang loop unroll(full)
|
| 635 |
+
for (int row = 0; row < row_tile; ++row) {
|
| 636 |
+
const std::int64_t input_row_offset =
|
| 637 |
+
(row_start + row) * args.in_features;
|
| 638 |
+
float accumulator = horizontal_sum(accumulators[row]);
|
| 639 |
+
if constexpr (!aligned_k) {
|
| 640 |
+
for (std::int64_t tail = k; tail < args.in_features; ++tail) {
|
| 641 |
+
const std::uint8_t packed = packed_row[tail / 2];
|
| 642 |
+
const std::uint8_t index =
|
| 643 |
+
(tail & 1) == 0 ? packed & 15u : (packed >> 4) & 15u;
|
| 644 |
+
const float activation = static_cast<float>(
|
| 645 |
+
static_cast<c10::BFloat16 const *>(args.x)[input_row_offset + tail]);
|
| 646 |
+
const float weight = static_cast<float>(
|
| 647 |
+
c10::BFloat16(args.centroids[index] * row_norm));
|
| 648 |
+
accumulator += activation * weight;
|
| 649 |
+
}
|
| 650 |
+
}
|
| 651 |
+
if (args.has_bias) {
|
| 652 |
+
accumulator += args.bias[out_col];
|
| 653 |
+
}
|
| 654 |
+
store_value<c10::BFloat16>(
|
| 655 |
+
args.out,
|
| 656 |
+
(row_start + row) * args.out_features + out_col,
|
| 657 |
+
accumulator);
|
| 658 |
+
}
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
template <int primary_row_tile, bool aligned_k, bool unroll_k>
|
| 662 |
+
ORBITQUANT_TARGET_AVX512_BF16 ORBITQUANT_NOINLINE void
|
| 663 |
+
packed_matmul_avx512_bf16_w4_typed(
|
| 664 |
+
PackedMatmulArgs const &args,
|
| 665 |
+
std::int64_t out_start,
|
| 666 |
+
std::int64_t out_end) {
|
| 667 |
+
static_assert(primary_row_tile == 4 || primary_row_tile == 8);
|
| 668 |
+
const std::int64_t packed_row_bytes = args.in_features / 2;
|
| 669 |
+
for (std::int64_t out_col = out_start; out_col < out_end; ++out_col) {
|
| 670 |
+
const auto *packed_row =
|
| 671 |
+
args.packed_weight_indices + out_col * packed_row_bytes;
|
| 672 |
+
std::int64_t row = 0;
|
| 673 |
+
for (; row + primary_row_tile <= args.rows; row += primary_row_tile) {
|
| 674 |
+
packed_matmul_avx512_bf16_w4_rows<primary_row_tile, aligned_k, unroll_k>(
|
| 675 |
+
args, packed_row, out_col, row);
|
| 676 |
+
}
|
| 677 |
+
if (row + 4 <= args.rows) {
|
| 678 |
+
packed_matmul_avx512_bf16_w4_rows<4, aligned_k, unroll_k>(
|
| 679 |
+
args, packed_row, out_col, row);
|
| 680 |
+
row += 4;
|
| 681 |
+
}
|
| 682 |
+
switch (args.rows - row) {
|
| 683 |
+
case 3:
|
| 684 |
+
packed_matmul_avx512_bf16_w4_rows<3, aligned_k, unroll_k>(
|
| 685 |
+
args, packed_row, out_col, row);
|
| 686 |
+
break;
|
| 687 |
+
case 2:
|
| 688 |
+
packed_matmul_avx512_bf16_w4_rows<2, aligned_k, unroll_k>(
|
| 689 |
+
args, packed_row, out_col, row);
|
| 690 |
+
break;
|
| 691 |
+
case 1:
|
| 692 |
+
packed_matmul_avx512_bf16_w4_rows<1, aligned_k, unroll_k>(
|
| 693 |
+
args, packed_row, out_col, row);
|
| 694 |
+
break;
|
| 695 |
+
default:
|
| 696 |
+
break;
|
| 697 |
+
}
|
| 698 |
+
}
|
| 699 |
+
}
|
| 700 |
+
#endif
|
| 701 |
+
|
| 702 |
+
bool runtime_has_avx512() {
|
| 703 |
+
#if defined(_MSC_VER)
|
| 704 |
+
int registers[4]{};
|
| 705 |
+
__cpuid(registers, 1);
|
| 706 |
+
const bool osxsave = (registers[2] & (1 << 27)) != 0;
|
| 707 |
+
const bool avx = (registers[2] & (1 << 28)) != 0;
|
| 708 |
+
const bool fma = (registers[2] & (1 << 12)) != 0;
|
| 709 |
+
const bool f16c = (registers[2] & (1 << 29)) != 0;
|
| 710 |
+
if (!osxsave || !avx || !fma || !f16c || (_xgetbv(0) & 0xE6) != 0xE6) {
|
| 711 |
+
return false;
|
| 712 |
+
}
|
| 713 |
+
__cpuidex(registers, 7, 0);
|
| 714 |
+
constexpr unsigned required_ebx =
|
| 715 |
+
(1u << 16) | (1u << 17) | (1u << 30) | (1u << 31);
|
| 716 |
+
return (static_cast<unsigned>(registers[1]) & required_ebx) == required_ebx;
|
| 717 |
+
#else
|
| 718 |
+
__builtin_cpu_init();
|
| 719 |
+
return __builtin_cpu_supports("avx512f") &&
|
| 720 |
+
__builtin_cpu_supports("avx512dq") &&
|
| 721 |
+
__builtin_cpu_supports("avx512bw") &&
|
| 722 |
+
__builtin_cpu_supports("avx512vl") &&
|
| 723 |
+
__builtin_cpu_supports("fma") && __builtin_cpu_supports("f16c");
|
| 724 |
+
#endif
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
bool runtime_has_avx512_bf16() {
|
| 728 |
+
#if defined(ORBITQUANT_HAS_AVX512_BF16_INTRINSICS)
|
| 729 |
+
__builtin_cpu_init();
|
| 730 |
+
return runtime_has_avx512() && __builtin_cpu_supports("avx512bf16");
|
| 731 |
+
#else
|
| 732 |
+
return false;
|
| 733 |
+
#endif
|
| 734 |
+
}
|
| 735 |
+
|
| 736 |
+
bool runtime_has_verified_amd_bf16_tuning() {
|
| 737 |
+
static const bool available = [] {
|
| 738 |
+
unsigned int eax = 0;
|
| 739 |
+
unsigned int ebx = 0;
|
| 740 |
+
unsigned int ecx = 0;
|
| 741 |
+
unsigned int edx = 0;
|
| 742 |
+
// CPUID vendor registers spell "AuthenticAMD" in EBX, EDX, ECX order.
|
| 743 |
+
if (!__get_cpuid(0, &eax, &ebx, &ecx, &edx) ||
|
| 744 |
+
ebx != 0x68747541u || edx != 0x69746e65u || ecx != 0x444d4163u ||
|
| 745 |
+
!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
|
| 746 |
+
return false;
|
| 747 |
+
}
|
| 748 |
+
const unsigned int base_family = (eax >> 8) & 0xfu;
|
| 749 |
+
const unsigned int family = base_family == 0xfu
|
| 750 |
+
? base_family + ((eax >> 20) & 0xffu)
|
| 751 |
+
: base_family;
|
| 752 |
+
// The four-row unrolled tile was measured on Zen 4 (EPYC 4564P); apply it
|
| 753 |
+
// to the AMD families that ship AVX-512 BF16 (Zen 4 = 19h, Zen 5 = 1Ah)
|
| 754 |
+
// instead of pinning the one benchmarked model.
|
| 755 |
+
return (family == 0x19u || family == 0x1au) && runtime_has_avx512_bf16();
|
| 756 |
+
}();
|
| 757 |
+
return available;
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
} // namespace
|
| 761 |
+
|
| 762 |
+
bool packed_matmul_x86_avx512_available() {
|
| 763 |
+
static const bool available = runtime_has_avx512();
|
| 764 |
+
return available;
|
| 765 |
+
}
|
| 766 |
+
|
| 767 |
+
void packed_matmul_x86_avx512_range(
|
| 768 |
+
PackedMatmulArgs const &args,
|
| 769 |
+
std::int64_t out_start,
|
| 770 |
+
std::int64_t out_end) {
|
| 771 |
+
if (!packed_matmul_x86_avx512_available()) {
|
| 772 |
+
packed_matmul_scalar_range(args, out_start, out_end);
|
| 773 |
+
return;
|
| 774 |
+
}
|
| 775 |
+
if (args.bits == 2 && args.in_features % 4 == 0) {
|
| 776 |
+
packed_matmul_avx512_lowbit_dispatch<W2Avx512Decoder>(
|
| 777 |
+
args, out_start, out_end);
|
| 778 |
+
return;
|
| 779 |
+
}
|
| 780 |
+
if (args.bits == 3 && args.in_features % 8 == 0) {
|
| 781 |
+
packed_matmul_avx512_lowbit_dispatch<W3Avx512Decoder>(
|
| 782 |
+
args, out_start, out_end);
|
| 783 |
+
return;
|
| 784 |
+
}
|
| 785 |
+
if (args.bits == 6 && args.in_features % 4 == 0) {
|
| 786 |
+
packed_matmul_avx512_lowbit_dispatch<W6Avx512Decoder>(
|
| 787 |
+
args, out_start, out_end);
|
| 788 |
+
return;
|
| 789 |
+
}
|
| 790 |
+
if (args.bits != 4 || args.in_features % 2 != 0) {
|
| 791 |
+
packed_matmul_scalar_range(args, out_start, out_end);
|
| 792 |
+
return;
|
| 793 |
+
}
|
| 794 |
+
switch (args.scalar_kind) {
|
| 795 |
+
case ScalarKind::Float32:
|
| 796 |
+
packed_matmul_avx512_w4_typed<float, load_float16>(
|
| 797 |
+
args, out_start, out_end);
|
| 798 |
+
return;
|
| 799 |
+
case ScalarKind::Float16:
|
| 800 |
+
packed_matmul_avx512_w4_typed<c10::Half, load_half16>(
|
| 801 |
+
args, out_start, out_end);
|
| 802 |
+
return;
|
| 803 |
+
case ScalarKind::BFloat16:
|
| 804 |
+
#if defined(ORBITQUANT_HAS_AVX512_BF16_INTRINSICS)
|
| 805 |
+
if (runtime_has_avx512_bf16()) {
|
| 806 |
+
const bool tuned_dimension = args.in_features == 1536 ||
|
| 807 |
+
args.in_features == 1920 || args.in_features == 3072;
|
| 808 |
+
const bool use_tuned_shape =
|
| 809 |
+
runtime_has_verified_amd_bf16_tuning() && args.rows >= 16 &&
|
| 810 |
+
tuned_dimension;
|
| 811 |
+
if (args.in_features % 32 == 0) {
|
| 812 |
+
if (use_tuned_shape) {
|
| 813 |
+
packed_matmul_avx512_bf16_w4_typed<4, true, true>(
|
| 814 |
+
args, out_start, out_end);
|
| 815 |
+
} else {
|
| 816 |
+
packed_matmul_avx512_bf16_w4_typed<8, true, false>(
|
| 817 |
+
args, out_start, out_end);
|
| 818 |
+
}
|
| 819 |
+
} else {
|
| 820 |
+
packed_matmul_avx512_bf16_w4_typed<8, false, false>(
|
| 821 |
+
args, out_start, out_end);
|
| 822 |
+
}
|
| 823 |
+
return;
|
| 824 |
+
}
|
| 825 |
+
#endif
|
| 826 |
+
packed_matmul_avx512_w4_typed<c10::BFloat16, load_bfloat16>(
|
| 827 |
+
args, out_start, out_end);
|
| 828 |
+
return;
|
| 829 |
+
}
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
} // namespace orbitquant::cpu
|
| 833 |
+
|
| 834 |
+
#else
|
| 835 |
+
|
| 836 |
+
namespace orbitquant::cpu {
|
| 837 |
+
|
| 838 |
+
bool packed_matmul_x86_avx512_available() {
|
| 839 |
+
return false;
|
| 840 |
+
}
|
| 841 |
+
|
| 842 |
+
void packed_matmul_x86_avx512_range(
|
| 843 |
+
PackedMatmulArgs const &args,
|
| 844 |
+
std::int64_t out_start,
|
| 845 |
+
std::int64_t out_end) {
|
| 846 |
+
packed_matmul_scalar_range(args, out_start, out_end);
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
} // namespace orbitquant::cpu
|
| 850 |
+
|
| 851 |
+
#endif
|
orbitquant_packed_matmul_cpu/quantize_activations_cpu.cpp
ADDED
|
@@ -0,0 +1,867 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "cpu_pool.h"
|
| 2 |
+
#include "cpu_threads.h"
|
| 3 |
+
#include "cpu_kernel_args.h"
|
| 4 |
+
#include "packed_matmul_cpu.h"
|
| 5 |
+
#include "../torch-ext/torch_binding.h"
|
| 6 |
+
|
| 7 |
+
#include <torch/headeronly/core/DeviceType.h>
|
| 8 |
+
#include <torch/headeronly/core/ScalarType.h>
|
| 9 |
+
#include <torch/headeronly/macros/Macros.h>
|
| 10 |
+
#include <torch/headeronly/util/BFloat16.h>
|
| 11 |
+
#include <torch/headeronly/util/Half.h>
|
| 12 |
+
|
| 13 |
+
#include <algorithm>
|
| 14 |
+
#include <array>
|
| 15 |
+
#include <cmath>
|
| 16 |
+
#include <cstdlib>
|
| 17 |
+
#include <cstdint>
|
| 18 |
+
#include <cstring>
|
| 19 |
+
#include <mutex>
|
| 20 |
+
#include <type_traits>
|
| 21 |
+
#include <utility>
|
| 22 |
+
#include <vector>
|
| 23 |
+
|
| 24 |
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
| 25 |
+
#include <arm_neon.h>
|
| 26 |
+
#endif
|
| 27 |
+
|
| 28 |
+
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER)
|
| 29 |
+
#include <immintrin.h>
|
| 30 |
+
#define ORBITQUANT_TARGET_AVX2 __attribute__((target("avx2,fma,f16c")))
|
| 31 |
+
#define ORBITQUANT_TARGET_AVX512 \
|
| 32 |
+
__attribute__((target("avx512f,avx512dq,avx512bw,avx512vl,fma,f16c")))
|
| 33 |
+
#endif
|
| 34 |
+
|
| 35 |
+
namespace {
|
| 36 |
+
|
| 37 |
+
using orbitquant::cpu::ActivationArgs;
|
| 38 |
+
using orbitquant::cpu::ActivationIsa;
|
| 39 |
+
|
| 40 |
+
ActivationIsa select_activation_isa() {
|
| 41 |
+
const char *requested = std::getenv("ORBITQUANT_CPU_ISA");
|
| 42 |
+
if (requested == nullptr || std::strcmp(requested, "auto") == 0) {
|
| 43 |
+
if (orbitquant::cpu::packed_matmul_x86_avx512_available()) {
|
| 44 |
+
return ActivationIsa::Avx512;
|
| 45 |
+
}
|
| 46 |
+
if (orbitquant::cpu::packed_matmul_x86_avx2_available()) {
|
| 47 |
+
return ActivationIsa::Avx2;
|
| 48 |
+
}
|
| 49 |
+
if (orbitquant::cpu::packed_matmul_neon_available()) {
|
| 50 |
+
return ActivationIsa::Neon;
|
| 51 |
+
}
|
| 52 |
+
return ActivationIsa::Portable;
|
| 53 |
+
}
|
| 54 |
+
if (std::strcmp(requested, "scalar") == 0) {
|
| 55 |
+
return ActivationIsa::Portable;
|
| 56 |
+
}
|
| 57 |
+
if (std::strcmp(requested, "avx2") == 0) {
|
| 58 |
+
STD_TORCH_CHECK(
|
| 59 |
+
orbitquant::cpu::packed_matmul_x86_avx2_available(),
|
| 60 |
+
"ORBITQUANT_CPU_ISA=avx2 requested AVX2/FMA/F16C on an unsupported CPU");
|
| 61 |
+
return ActivationIsa::Avx2;
|
| 62 |
+
}
|
| 63 |
+
if (std::strcmp(requested, "avx512") == 0) {
|
| 64 |
+
STD_TORCH_CHECK(
|
| 65 |
+
orbitquant::cpu::packed_matmul_x86_avx512_available(),
|
| 66 |
+
"ORBITQUANT_CPU_ISA=avx512 requested AVX-512F/DQ/BW/VL on an unsupported CPU");
|
| 67 |
+
return ActivationIsa::Avx512;
|
| 68 |
+
}
|
| 69 |
+
if (std::strcmp(requested, "neon") == 0) {
|
| 70 |
+
STD_TORCH_CHECK(
|
| 71 |
+
orbitquant::cpu::packed_matmul_neon_available(),
|
| 72 |
+
"ORBITQUANT_CPU_ISA=neon requested NEON on an unsupported CPU");
|
| 73 |
+
return ActivationIsa::Neon;
|
| 74 |
+
}
|
| 75 |
+
STD_TORCH_CHECK(
|
| 76 |
+
false,
|
| 77 |
+
"ORBITQUANT_CPU_ISA must be auto, scalar, avx2, avx512, or neon");
|
| 78 |
+
return ActivationIsa::Portable;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
orbitquant::cpu::ScalarKind activation_scalar_kind(
|
| 82 |
+
OrbitQuantTensor const &tensor) {
|
| 83 |
+
using torch::headeronly::ScalarType;
|
| 84 |
+
switch (tensor.scalar_type()) {
|
| 85 |
+
case ScalarType::Float:
|
| 86 |
+
return orbitquant::cpu::ScalarKind::Float32;
|
| 87 |
+
case ScalarType::Half:
|
| 88 |
+
return orbitquant::cpu::ScalarKind::Float16;
|
| 89 |
+
case ScalarType::BFloat16:
|
| 90 |
+
return orbitquant::cpu::ScalarKind::BFloat16;
|
| 91 |
+
default:
|
| 92 |
+
STD_TORCH_CHECK(
|
| 93 |
+
false,
|
| 94 |
+
"CPU activation quantization supports float32, float16, and bfloat16 inputs");
|
| 95 |
+
}
|
| 96 |
+
return orbitquant::cpu::ScalarKind::Float32;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
template <typename scalar_t>
|
| 100 |
+
inline float load_scalar(void const *data, std::int64_t offset) {
|
| 101 |
+
return static_cast<float>(static_cast<scalar_t const *>(data)[offset]);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
template <>
|
| 105 |
+
inline float load_scalar<float>(void const *data, std::int64_t offset) {
|
| 106 |
+
return static_cast<float const *>(data)[offset];
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
template <typename scalar_t>
|
| 110 |
+
inline void store_scalar(void *data, std::int64_t offset, float value) {
|
| 111 |
+
static_cast<scalar_t *>(data)[offset] = scalar_t(value);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
template <>
|
| 115 |
+
inline void store_scalar<float>(void *data, std::int64_t offset, float value) {
|
| 116 |
+
static_cast<float *>(data)[offset] = value;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
inline void fwht_block_portable(float *values, std::int64_t block_size) {
|
| 120 |
+
for (std::int64_t half = 1; half < block_size; half *= 2) {
|
| 121 |
+
for (std::int64_t base = 0; base < block_size; base += 2 * half) {
|
| 122 |
+
for (std::int64_t offset = 0; offset < half; ++offset) {
|
| 123 |
+
const float left = values[base + offset];
|
| 124 |
+
const float right = values[base + half + offset];
|
| 125 |
+
values[base + offset] = left + right;
|
| 126 |
+
values[base + half + offset] = left - right;
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
| 133 |
+
inline void fwht_block_neon(float *values, std::int64_t block_size) {
|
| 134 |
+
if (block_size < 4) {
|
| 135 |
+
fwht_block_portable(values, block_size);
|
| 136 |
+
return;
|
| 137 |
+
}
|
| 138 |
+
const uint32x4_t odd_lanes = {0u, 0xFFFFFFFFu, 0u, 0xFFFFFFFFu};
|
| 139 |
+
const uint32x4_t high_lanes = {0u, 0u, 0xFFFFFFFFu, 0xFFFFFFFFu};
|
| 140 |
+
for (std::int64_t base = 0; base < block_size; base += 4) {
|
| 141 |
+
float32x4_t value = vld1q_f32(values + base);
|
| 142 |
+
float32x4_t swapped = vrev64q_f32(value);
|
| 143 |
+
value = vbslq_f32(
|
| 144 |
+
odd_lanes, vsubq_f32(swapped, value), vaddq_f32(value, swapped));
|
| 145 |
+
swapped = vextq_f32(value, value, 2);
|
| 146 |
+
value = vbslq_f32(
|
| 147 |
+
high_lanes, vsubq_f32(swapped, value), vaddq_f32(value, swapped));
|
| 148 |
+
vst1q_f32(values + base, value);
|
| 149 |
+
}
|
| 150 |
+
for (std::int64_t half = 4; half < block_size; half *= 2) {
|
| 151 |
+
for (std::int64_t base = 0; base < block_size; base += 2 * half) {
|
| 152 |
+
for (std::int64_t offset = 0; offset + 4 <= half; offset += 4) {
|
| 153 |
+
const float32x4_t left = vld1q_f32(values + base + offset);
|
| 154 |
+
const float32x4_t right = vld1q_f32(values + base + half + offset);
|
| 155 |
+
vst1q_f32(values + base + offset, vaddq_f32(left, right));
|
| 156 |
+
vst1q_f32(values + base + half + offset, vsubq_f32(left, right));
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
#endif
|
| 162 |
+
|
| 163 |
+
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER)
|
| 164 |
+
ORBITQUANT_TARGET_AVX2 void fwht_block_avx2(
|
| 165 |
+
float *values,
|
| 166 |
+
std::int64_t block_size) {
|
| 167 |
+
if (block_size < 8) {
|
| 168 |
+
fwht_block_portable(values, block_size);
|
| 169 |
+
return;
|
| 170 |
+
}
|
| 171 |
+
for (std::int64_t base = 0; base < block_size; base += 8) {
|
| 172 |
+
__m256 value = _mm256_loadu_ps(values + base);
|
| 173 |
+
__m256 swapped = _mm256_permute_ps(value, 0xB1);
|
| 174 |
+
value = _mm256_blend_ps(
|
| 175 |
+
_mm256_add_ps(value, swapped), _mm256_sub_ps(swapped, value), 0xAA);
|
| 176 |
+
swapped = _mm256_permute_ps(value, 0x4E);
|
| 177 |
+
value = _mm256_blend_ps(
|
| 178 |
+
_mm256_add_ps(value, swapped), _mm256_sub_ps(swapped, value), 0xCC);
|
| 179 |
+
swapped = _mm256_permute2f128_ps(value, value, 0x01);
|
| 180 |
+
value = _mm256_blend_ps(
|
| 181 |
+
_mm256_add_ps(value, swapped), _mm256_sub_ps(swapped, value), 0xF0);
|
| 182 |
+
_mm256_storeu_ps(values + base, value);
|
| 183 |
+
}
|
| 184 |
+
for (std::int64_t half = 8; half < block_size; half *= 2) {
|
| 185 |
+
for (std::int64_t base = 0; base < block_size; base += 2 * half) {
|
| 186 |
+
for (std::int64_t offset = 0; offset + 8 <= half; offset += 8) {
|
| 187 |
+
const __m256 left = _mm256_loadu_ps(values + base + offset);
|
| 188 |
+
const __m256 right =
|
| 189 |
+
_mm256_loadu_ps(values + base + half + offset);
|
| 190 |
+
_mm256_storeu_ps(values + base + offset, _mm256_add_ps(left, right));
|
| 191 |
+
_mm256_storeu_ps(
|
| 192 |
+
values + base + half + offset,
|
| 193 |
+
_mm256_sub_ps(left, right));
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
ORBITQUANT_TARGET_AVX512 void fwht_block_avx512(
|
| 200 |
+
float *values,
|
| 201 |
+
std::int64_t block_size) {
|
| 202 |
+
if (block_size < 16) {
|
| 203 |
+
fwht_block_portable(values, block_size);
|
| 204 |
+
return;
|
| 205 |
+
}
|
| 206 |
+
// Butterfly widths 1-8 stay inside one 16-float register: swap the paired
|
| 207 |
+
// lanes with shuffles and blend the +/- results, one pass over memory
|
| 208 |
+
// instead of four.
|
| 209 |
+
for (std::int64_t base = 0; base < block_size; base += 16) {
|
| 210 |
+
__m512 value = _mm512_loadu_ps(values + base);
|
| 211 |
+
__m512 swapped = _mm512_permute_ps(value, 0xB1);
|
| 212 |
+
value = _mm512_mask_blend_ps(
|
| 213 |
+
0xAAAA, _mm512_add_ps(value, swapped), _mm512_sub_ps(swapped, value));
|
| 214 |
+
swapped = _mm512_permute_ps(value, 0x4E);
|
| 215 |
+
value = _mm512_mask_blend_ps(
|
| 216 |
+
0xCCCC, _mm512_add_ps(value, swapped), _mm512_sub_ps(swapped, value));
|
| 217 |
+
swapped = _mm512_shuffle_f32x4(value, value, 0xB1);
|
| 218 |
+
value = _mm512_mask_blend_ps(
|
| 219 |
+
0xF0F0, _mm512_add_ps(value, swapped), _mm512_sub_ps(swapped, value));
|
| 220 |
+
swapped = _mm512_shuffle_f32x4(value, value, 0x4E);
|
| 221 |
+
value = _mm512_mask_blend_ps(
|
| 222 |
+
0xFF00, _mm512_add_ps(value, swapped), _mm512_sub_ps(swapped, value));
|
| 223 |
+
_mm512_storeu_ps(values + base, value);
|
| 224 |
+
}
|
| 225 |
+
for (std::int64_t half = 16; half < block_size; half *= 2) {
|
| 226 |
+
for (std::int64_t base = 0; base < block_size; base += 2 * half) {
|
| 227 |
+
for (std::int64_t offset = 0; offset + 16 <= half; offset += 16) {
|
| 228 |
+
const __m512 left = _mm512_loadu_ps(values + base + offset);
|
| 229 |
+
const __m512 right =
|
| 230 |
+
_mm512_loadu_ps(values + base + half + offset);
|
| 231 |
+
_mm512_storeu_ps(values + base + offset, _mm512_add_ps(left, right));
|
| 232 |
+
_mm512_storeu_ps(
|
| 233 |
+
values + base + half + offset,
|
| 234 |
+
_mm512_sub_ps(left, right));
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
#endif
|
| 240 |
+
|
| 241 |
+
inline void fwht_block(
|
| 242 |
+
float *values,
|
| 243 |
+
std::int64_t block_size,
|
| 244 |
+
ActivationIsa isa) {
|
| 245 |
+
#if defined(__x86_64__) || defined(_M_X64)
|
| 246 |
+
if (isa == ActivationIsa::Avx512) {
|
| 247 |
+
#if defined(_MSC_VER)
|
| 248 |
+
orbitquant::cpu::activation_fwht_msvc_avx2(values, block_size);
|
| 249 |
+
#else
|
| 250 |
+
fwht_block_avx512(values, block_size);
|
| 251 |
+
#endif
|
| 252 |
+
return;
|
| 253 |
+
}
|
| 254 |
+
if (isa == ActivationIsa::Avx2) {
|
| 255 |
+
#if defined(_MSC_VER)
|
| 256 |
+
orbitquant::cpu::activation_fwht_msvc_avx2(values, block_size);
|
| 257 |
+
#else
|
| 258 |
+
fwht_block_avx2(values, block_size);
|
| 259 |
+
#endif
|
| 260 |
+
return;
|
| 261 |
+
}
|
| 262 |
+
#endif
|
| 263 |
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
| 264 |
+
if (isa == ActivationIsa::Neon) {
|
| 265 |
+
fwht_block_neon(values, block_size);
|
| 266 |
+
return;
|
| 267 |
+
}
|
| 268 |
+
#endif
|
| 269 |
+
fwht_block_portable(values, block_size);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
template <typename scalar_t>
|
| 273 |
+
float squared_norm_scalar(void const *data, std::int64_t offset, std::int64_t dim) {
|
| 274 |
+
float result = 0.0f;
|
| 275 |
+
for (std::int64_t index = 0; index < dim; ++index) {
|
| 276 |
+
const float value = load_scalar<scalar_t>(data, offset + index);
|
| 277 |
+
result += value * value;
|
| 278 |
+
}
|
| 279 |
+
return result;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
| 283 |
+
template <typename scalar_t>
|
| 284 |
+
float squared_norm_neon(void const *data, std::int64_t offset, std::int64_t dim) {
|
| 285 |
+
float32x4_t accumulator = vdupq_n_f32(0.0f);
|
| 286 |
+
std::int64_t index = 0;
|
| 287 |
+
for (; index + 4 <= dim; index += 4) {
|
| 288 |
+
float32x4_t values;
|
| 289 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 290 |
+
values = vld1q_f32(static_cast<float const *>(data) + offset + index);
|
| 291 |
+
} else if constexpr (std::is_same_v<scalar_t, c10::Half>) {
|
| 292 |
+
const auto *source = reinterpret_cast<float16_t const *>(
|
| 293 |
+
static_cast<std::uint16_t const *>(data) + offset + index);
|
| 294 |
+
values = vcvt_f32_f16(vld1_f16(source));
|
| 295 |
+
} else {
|
| 296 |
+
const uint16x4_t raw = vld1_u16(
|
| 297 |
+
static_cast<std::uint16_t const *>(data) + offset + index);
|
| 298 |
+
values = vreinterpretq_f32_u32(vshlq_n_u32(vmovl_u16(raw), 16));
|
| 299 |
+
}
|
| 300 |
+
accumulator = vfmaq_f32(accumulator, values, values);
|
| 301 |
+
}
|
| 302 |
+
float result = vaddvq_f32(accumulator);
|
| 303 |
+
for (; index < dim; ++index) {
|
| 304 |
+
const float value = load_scalar<scalar_t>(data, offset + index);
|
| 305 |
+
result += value * value;
|
| 306 |
+
}
|
| 307 |
+
return result;
|
| 308 |
+
}
|
| 309 |
+
#endif
|
| 310 |
+
|
| 311 |
+
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER)
|
| 312 |
+
template <typename scalar_t>
|
| 313 |
+
ORBITQUANT_TARGET_AVX2 float squared_norm_avx2(
|
| 314 |
+
void const *data,
|
| 315 |
+
std::int64_t offset,
|
| 316 |
+
std::int64_t dim) {
|
| 317 |
+
__m256 accumulator = _mm256_setzero_ps();
|
| 318 |
+
std::int64_t index = 0;
|
| 319 |
+
for (; index + 8 <= dim; index += 8) {
|
| 320 |
+
__m256 values;
|
| 321 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 322 |
+
values = _mm256_loadu_ps(
|
| 323 |
+
static_cast<float const *>(data) + offset + index);
|
| 324 |
+
} else if constexpr (std::is_same_v<scalar_t, c10::Half>) {
|
| 325 |
+
const auto *source =
|
| 326 |
+
static_cast<std::uint16_t const *>(data) + offset + index;
|
| 327 |
+
values = _mm256_cvtph_ps(
|
| 328 |
+
_mm_loadu_si128(reinterpret_cast<__m128i const *>(source)));
|
| 329 |
+
} else {
|
| 330 |
+
const auto *source =
|
| 331 |
+
static_cast<std::uint16_t const *>(data) + offset + index;
|
| 332 |
+
const __m128i packed =
|
| 333 |
+
_mm_loadu_si128(reinterpret_cast<__m128i const *>(source));
|
| 334 |
+
values = _mm256_castsi256_ps(
|
| 335 |
+
_mm256_slli_epi32(_mm256_cvtepu16_epi32(packed), 16));
|
| 336 |
+
}
|
| 337 |
+
accumulator = _mm256_fmadd_ps(values, values, accumulator);
|
| 338 |
+
}
|
| 339 |
+
const __m128 halves = _mm_add_ps(
|
| 340 |
+
_mm256_castps256_ps128(accumulator),
|
| 341 |
+
_mm256_extractf128_ps(accumulator, 1));
|
| 342 |
+
const __m128 pairs = _mm_hadd_ps(halves, halves);
|
| 343 |
+
float result = _mm_cvtss_f32(_mm_hadd_ps(pairs, pairs));
|
| 344 |
+
for (; index < dim; ++index) {
|
| 345 |
+
const float value = load_scalar<scalar_t>(data, offset + index);
|
| 346 |
+
result += value * value;
|
| 347 |
+
}
|
| 348 |
+
return result;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
template <typename scalar_t>
|
| 352 |
+
ORBITQUANT_TARGET_AVX512 float squared_norm_avx512(
|
| 353 |
+
void const *data,
|
| 354 |
+
std::int64_t offset,
|
| 355 |
+
std::int64_t dim) {
|
| 356 |
+
__m512 accumulator = _mm512_setzero_ps();
|
| 357 |
+
std::int64_t index = 0;
|
| 358 |
+
for (; index + 16 <= dim; index += 16) {
|
| 359 |
+
__m512 values;
|
| 360 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 361 |
+
values = _mm512_loadu_ps(
|
| 362 |
+
static_cast<float const *>(data) + offset + index);
|
| 363 |
+
} else if constexpr (std::is_same_v<scalar_t, c10::Half>) {
|
| 364 |
+
const auto *source =
|
| 365 |
+
static_cast<std::uint16_t const *>(data) + offset + index;
|
| 366 |
+
values = _mm512_cvtph_ps(
|
| 367 |
+
_mm256_loadu_si256(reinterpret_cast<__m256i const *>(source)));
|
| 368 |
+
} else {
|
| 369 |
+
const auto *source =
|
| 370 |
+
static_cast<std::uint16_t const *>(data) + offset + index;
|
| 371 |
+
const __m256i packed =
|
| 372 |
+
_mm256_loadu_si256(reinterpret_cast<__m256i const *>(source));
|
| 373 |
+
values = _mm512_castsi512_ps(
|
| 374 |
+
_mm512_slli_epi32(_mm512_cvtepu16_epi32(packed), 16));
|
| 375 |
+
}
|
| 376 |
+
accumulator = _mm512_fmadd_ps(values, values, accumulator);
|
| 377 |
+
}
|
| 378 |
+
float result = _mm512_reduce_add_ps(accumulator);
|
| 379 |
+
for (; index < dim; ++index) {
|
| 380 |
+
const float value = load_scalar<scalar_t>(data, offset + index);
|
| 381 |
+
result += value * value;
|
| 382 |
+
}
|
| 383 |
+
return result;
|
| 384 |
+
}
|
| 385 |
+
#endif
|
| 386 |
+
|
| 387 |
+
template <typename scalar_t>
|
| 388 |
+
float squared_norm(
|
| 389 |
+
void const *data,
|
| 390 |
+
std::int64_t offset,
|
| 391 |
+
std::int64_t dim,
|
| 392 |
+
ActivationIsa isa) {
|
| 393 |
+
#if defined(__x86_64__) || defined(_M_X64)
|
| 394 |
+
if (isa == ActivationIsa::Avx512) {
|
| 395 |
+
#if defined(_MSC_VER)
|
| 396 |
+
constexpr auto scalar_kind = std::is_same_v<scalar_t, float>
|
| 397 |
+
? orbitquant::cpu::ScalarKind::Float32
|
| 398 |
+
: std::is_same_v<scalar_t, c10::Half>
|
| 399 |
+
? orbitquant::cpu::ScalarKind::Float16
|
| 400 |
+
: orbitquant::cpu::ScalarKind::BFloat16;
|
| 401 |
+
return orbitquant::cpu::activation_squared_norm_msvc_avx2(
|
| 402 |
+
data, scalar_kind, offset, dim);
|
| 403 |
+
#else
|
| 404 |
+
return squared_norm_avx512<scalar_t>(data, offset, dim);
|
| 405 |
+
#endif
|
| 406 |
+
}
|
| 407 |
+
if (isa == ActivationIsa::Avx2) {
|
| 408 |
+
#if defined(_MSC_VER)
|
| 409 |
+
constexpr auto scalar_kind = std::is_same_v<scalar_t, float>
|
| 410 |
+
? orbitquant::cpu::ScalarKind::Float32
|
| 411 |
+
: std::is_same_v<scalar_t, c10::Half>
|
| 412 |
+
? orbitquant::cpu::ScalarKind::Float16
|
| 413 |
+
: orbitquant::cpu::ScalarKind::BFloat16;
|
| 414 |
+
return orbitquant::cpu::activation_squared_norm_msvc_avx2(
|
| 415 |
+
data, scalar_kind, offset, dim);
|
| 416 |
+
#else
|
| 417 |
+
return squared_norm_avx2<scalar_t>(data, offset, dim);
|
| 418 |
+
#endif
|
| 419 |
+
}
|
| 420 |
+
#endif
|
| 421 |
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
| 422 |
+
if (isa == ActivationIsa::Neon) {
|
| 423 |
+
return squared_norm_neon<scalar_t>(data, offset, dim);
|
| 424 |
+
}
|
| 425 |
+
#endif
|
| 426 |
+
return squared_norm_scalar<scalar_t>(data, offset, dim);
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
inline std::int64_t nearest_centroid(
|
| 430 |
+
float value,
|
| 431 |
+
float const *boundaries,
|
| 432 |
+
std::int64_t boundary_count) {
|
| 433 |
+
std::int64_t low = 0;
|
| 434 |
+
std::int64_t high = boundary_count;
|
| 435 |
+
while (low < high) {
|
| 436 |
+
const std::int64_t middle = low + (high - low) / 2;
|
| 437 |
+
if (value <= boundaries[middle]) {
|
| 438 |
+
high = middle;
|
| 439 |
+
} else {
|
| 440 |
+
low = middle + 1;
|
| 441 |
+
}
|
| 442 |
+
}
|
| 443 |
+
return low;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
template <typename scalar_t>
|
| 447 |
+
void quantize_lookup_scalar(
|
| 448 |
+
ActivationArgs const &args,
|
| 449 |
+
float const *scratch,
|
| 450 |
+
std::int64_t output_offset,
|
| 451 |
+
float norm,
|
| 452 |
+
std::int64_t start = 0) {
|
| 453 |
+
for (std::int64_t index = start; index < args.dim; ++index) {
|
| 454 |
+
const float direction = scratch[index] * args.inv_sqrt_block;
|
| 455 |
+
const std::int64_t centroid_index =
|
| 456 |
+
nearest_centroid(direction, args.boundaries, args.boundary_count);
|
| 457 |
+
store_scalar<scalar_t>(
|
| 458 |
+
args.out,
|
| 459 |
+
output_offset + index,
|
| 460 |
+
args.centroids[centroid_index] * norm);
|
| 461 |
+
}
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER)
|
| 465 |
+
ORBITQUANT_TARGET_AVX2 inline __m128i float8_to_bfloat8(__m256 values) {
|
| 466 |
+
const __m256i bits = _mm256_castps_si256(values);
|
| 467 |
+
const __m256i absolute_bits =
|
| 468 |
+
_mm256_and_si256(bits, _mm256_set1_epi32(0x7fffffff));
|
| 469 |
+
const __m256i nan_mask =
|
| 470 |
+
_mm256_cmpgt_epi32(absolute_bits, _mm256_set1_epi32(0x7f800000));
|
| 471 |
+
const __m256i rounding = _mm256_add_epi32(
|
| 472 |
+
_mm256_set1_epi32(0x7fff),
|
| 473 |
+
_mm256_and_si256(_mm256_srli_epi32(bits, 16), _mm256_set1_epi32(1)));
|
| 474 |
+
const __m256i upper = _mm256_srli_epi32(
|
| 475 |
+
_mm256_add_epi32(bits, rounding),
|
| 476 |
+
16);
|
| 477 |
+
__m128i packed = _mm_packus_epi32(
|
| 478 |
+
_mm256_castsi256_si128(upper),
|
| 479 |
+
_mm256_extracti128_si256(upper, 1));
|
| 480 |
+
const __m128i packed_nan_mask = _mm_packs_epi32(
|
| 481 |
+
_mm256_castsi256_si128(nan_mask),
|
| 482 |
+
_mm256_extracti128_si256(nan_mask, 1));
|
| 483 |
+
packed = _mm_blendv_epi8(
|
| 484 |
+
packed,
|
| 485 |
+
_mm_set1_epi16(0x7fc0),
|
| 486 |
+
packed_nan_mask);
|
| 487 |
+
return packed;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
ORBITQUANT_TARGET_AVX512 inline __m256i float16_to_bfloat16(__m512 values) {
|
| 491 |
+
const __m512i bits = _mm512_castps_si512(values);
|
| 492 |
+
const __m512i absolute_bits =
|
| 493 |
+
_mm512_and_si512(bits, _mm512_set1_epi32(0x7fffffff));
|
| 494 |
+
const __mmask16 nan_mask = _mm512_cmp_epu32_mask(
|
| 495 |
+
absolute_bits,
|
| 496 |
+
_mm512_set1_epi32(0x7f800000),
|
| 497 |
+
_MM_CMPINT_GT);
|
| 498 |
+
const __m512i rounding = _mm512_add_epi32(
|
| 499 |
+
_mm512_set1_epi32(0x7fff),
|
| 500 |
+
_mm512_and_si512(_mm512_srli_epi32(bits, 16), _mm512_set1_epi32(1)));
|
| 501 |
+
const __m512i upper = _mm512_srli_epi32(
|
| 502 |
+
_mm512_add_epi32(bits, rounding),
|
| 503 |
+
16);
|
| 504 |
+
return _mm256_mask_mov_epi16(
|
| 505 |
+
_mm512_cvtepi32_epi16(upper),
|
| 506 |
+
nan_mask,
|
| 507 |
+
_mm256_set1_epi16(0x7fc0));
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
template <typename scalar_t>
|
| 511 |
+
ORBITQUANT_TARGET_AVX2 void quantize_lookup_avx2(
|
| 512 |
+
ActivationArgs const &args,
|
| 513 |
+
float const *scratch,
|
| 514 |
+
std::int64_t output_offset,
|
| 515 |
+
float norm) {
|
| 516 |
+
const __m256 inverse_sqrt_block = _mm256_set1_ps(args.inv_sqrt_block);
|
| 517 |
+
const __m256 output_norm = _mm256_set1_ps(norm);
|
| 518 |
+
const __m256i ones = _mm256_set1_epi32(1);
|
| 519 |
+
std::int64_t index = 0;
|
| 520 |
+
for (; index + 8 <= args.dim; index += 8) {
|
| 521 |
+
const __m256 direction = _mm256_mul_ps(
|
| 522 |
+
_mm256_loadu_ps(scratch + index), inverse_sqrt_block);
|
| 523 |
+
__m256i centroid_indices = _mm256_setzero_si256();
|
| 524 |
+
for (std::int64_t boundary = 0; boundary < args.boundary_count; ++boundary) {
|
| 525 |
+
const __m256 comparison = _mm256_cmp_ps(
|
| 526 |
+
direction,
|
| 527 |
+
_mm256_set1_ps(args.boundaries[boundary]),
|
| 528 |
+
_CMP_NLE_UQ);
|
| 529 |
+
centroid_indices = _mm256_add_epi32(
|
| 530 |
+
centroid_indices,
|
| 531 |
+
_mm256_and_si256(_mm256_castps_si256(comparison), ones));
|
| 532 |
+
}
|
| 533 |
+
const __m256 output = _mm256_mul_ps(
|
| 534 |
+
_mm256_i32gather_ps(args.centroids, centroid_indices, 4), output_norm);
|
| 535 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 536 |
+
_mm256_storeu_ps(
|
| 537 |
+
static_cast<float *>(args.out) + output_offset + index,
|
| 538 |
+
output);
|
| 539 |
+
} else if constexpr (std::is_same_v<scalar_t, c10::Half>) {
|
| 540 |
+
_mm_storeu_si128(
|
| 541 |
+
reinterpret_cast<__m128i *>(
|
| 542 |
+
static_cast<std::uint16_t *>(args.out) + output_offset + index),
|
| 543 |
+
_mm256_cvtps_ph(output, _MM_FROUND_TO_NEAREST_INT));
|
| 544 |
+
} else {
|
| 545 |
+
_mm_storeu_si128(
|
| 546 |
+
reinterpret_cast<__m128i *>(
|
| 547 |
+
static_cast<std::uint16_t *>(args.out) + output_offset + index),
|
| 548 |
+
float8_to_bfloat8(output));
|
| 549 |
+
}
|
| 550 |
+
}
|
| 551 |
+
quantize_lookup_scalar<scalar_t>(args, scratch, output_offset, norm, index);
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
template <typename scalar_t>
|
| 555 |
+
ORBITQUANT_TARGET_AVX512 void quantize_lookup_avx512(
|
| 556 |
+
ActivationArgs const &args,
|
| 557 |
+
float const *scratch,
|
| 558 |
+
std::int64_t output_offset,
|
| 559 |
+
float norm) {
|
| 560 |
+
const __m512 inverse_sqrt_block = _mm512_set1_ps(args.inv_sqrt_block);
|
| 561 |
+
const __m512 output_norm = _mm512_set1_ps(norm);
|
| 562 |
+
const __m512i ones = _mm512_set1_epi32(1);
|
| 563 |
+
std::int64_t index = 0;
|
| 564 |
+
for (; index + 16 <= args.dim; index += 16) {
|
| 565 |
+
const __m512 direction = _mm512_mul_ps(
|
| 566 |
+
_mm512_loadu_ps(scratch + index), inverse_sqrt_block);
|
| 567 |
+
__m512i centroid_indices = _mm512_setzero_si512();
|
| 568 |
+
for (std::int64_t boundary = 0; boundary < args.boundary_count; ++boundary) {
|
| 569 |
+
const __mmask16 comparison = _mm512_cmp_ps_mask(
|
| 570 |
+
direction,
|
| 571 |
+
_mm512_set1_ps(args.boundaries[boundary]),
|
| 572 |
+
_CMP_NLE_UQ);
|
| 573 |
+
centroid_indices = _mm512_mask_add_epi32(
|
| 574 |
+
centroid_indices,
|
| 575 |
+
comparison,
|
| 576 |
+
centroid_indices,
|
| 577 |
+
ones);
|
| 578 |
+
}
|
| 579 |
+
const __m512 output = _mm512_mul_ps(
|
| 580 |
+
_mm512_i32gather_ps(centroid_indices, args.centroids, 4),
|
| 581 |
+
output_norm);
|
| 582 |
+
if constexpr (std::is_same_v<scalar_t, float>) {
|
| 583 |
+
_mm512_storeu_ps(
|
| 584 |
+
static_cast<float *>(args.out) + output_offset + index,
|
| 585 |
+
output);
|
| 586 |
+
} else if constexpr (std::is_same_v<scalar_t, c10::Half>) {
|
| 587 |
+
_mm256_storeu_si256(
|
| 588 |
+
reinterpret_cast<__m256i *>(
|
| 589 |
+
static_cast<std::uint16_t *>(args.out) + output_offset + index),
|
| 590 |
+
_mm512_cvtps_ph(output, _MM_FROUND_TO_NEAREST_INT));
|
| 591 |
+
} else {
|
| 592 |
+
_mm256_storeu_si256(
|
| 593 |
+
reinterpret_cast<__m256i *>(
|
| 594 |
+
static_cast<std::uint16_t *>(args.out) + output_offset + index),
|
| 595 |
+
float16_to_bfloat16(output));
|
| 596 |
+
}
|
| 597 |
+
}
|
| 598 |
+
quantize_lookup_scalar<scalar_t>(args, scratch, output_offset, norm, index);
|
| 599 |
+
}
|
| 600 |
+
#endif
|
| 601 |
+
|
| 602 |
+
template <typename scalar_t>
|
| 603 |
+
void quantize_lookup(
|
| 604 |
+
ActivationArgs const &args,
|
| 605 |
+
float const *scratch,
|
| 606 |
+
std::int64_t output_offset,
|
| 607 |
+
float norm,
|
| 608 |
+
ActivationIsa isa) {
|
| 609 |
+
#if defined(__x86_64__) || defined(_M_X64)
|
| 610 |
+
if (isa == ActivationIsa::Avx512) {
|
| 611 |
+
#if defined(_MSC_VER)
|
| 612 |
+
orbitquant::cpu::activation_quantize_lookup_msvc_avx2(
|
| 613 |
+
args, scratch, output_offset, norm);
|
| 614 |
+
#else
|
| 615 |
+
quantize_lookup_avx512<scalar_t>(args, scratch, output_offset, norm);
|
| 616 |
+
#endif
|
| 617 |
+
return;
|
| 618 |
+
}
|
| 619 |
+
if (isa == ActivationIsa::Avx2) {
|
| 620 |
+
#if defined(_MSC_VER)
|
| 621 |
+
orbitquant::cpu::activation_quantize_lookup_msvc_avx2(
|
| 622 |
+
args, scratch, output_offset, norm);
|
| 623 |
+
#else
|
| 624 |
+
quantize_lookup_avx2<scalar_t>(args, scratch, output_offset, norm);
|
| 625 |
+
#endif
|
| 626 |
+
return;
|
| 627 |
+
}
|
| 628 |
+
#endif
|
| 629 |
+
quantize_lookup_scalar<scalar_t>(args, scratch, output_offset, norm);
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
template <typename scalar_t, typename index_t>
|
| 633 |
+
void quantize_activation_range(
|
| 634 |
+
ActivationArgs const &args,
|
| 635 |
+
index_t const *permutation,
|
| 636 |
+
std::int64_t row_start,
|
| 637 |
+
std::int64_t row_end) {
|
| 638 |
+
thread_local std::vector<float> scratch;
|
| 639 |
+
if (scratch.size() < static_cast<std::size_t>(args.dim)) {
|
| 640 |
+
scratch.resize(static_cast<std::size_t>(args.dim));
|
| 641 |
+
}
|
| 642 |
+
for (std::int64_t row = row_start; row < row_end; ++row) {
|
| 643 |
+
const std::int64_t input_offset = row * args.dim;
|
| 644 |
+
const float norm_squared =
|
| 645 |
+
squared_norm<scalar_t>(args.x, input_offset, args.dim, args.isa);
|
| 646 |
+
const float norm = std::sqrt(norm_squared);
|
| 647 |
+
const float inverse_norm = 1.0f / (norm + args.eps);
|
| 648 |
+
for (std::int64_t index = 0; index < args.dim; ++index) {
|
| 649 |
+
const float value = load_scalar<scalar_t>(
|
| 650 |
+
args.x,
|
| 651 |
+
input_offset + static_cast<std::int64_t>(permutation[index]));
|
| 652 |
+
scratch[index] = value * static_cast<float>(args.signs[index]) * inverse_norm;
|
| 653 |
+
}
|
| 654 |
+
for (std::int64_t block = 0; block < args.dim; block += args.block_size) {
|
| 655 |
+
fwht_block(scratch.data() + block, args.block_size, args.isa);
|
| 656 |
+
}
|
| 657 |
+
quantize_lookup<scalar_t>(
|
| 658 |
+
args,
|
| 659 |
+
scratch.data(),
|
| 660 |
+
input_offset,
|
| 661 |
+
norm,
|
| 662 |
+
args.isa);
|
| 663 |
+
}
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
template <typename index_t>
|
| 667 |
+
void quantize_activation_dispatch_indexed(
|
| 668 |
+
ActivationArgs const &args,
|
| 669 |
+
index_t const *permutation,
|
| 670 |
+
std::int64_t row_start,
|
| 671 |
+
std::int64_t row_end) {
|
| 672 |
+
switch (args.scalar_kind) {
|
| 673 |
+
case orbitquant::cpu::ScalarKind::Float32:
|
| 674 |
+
quantize_activation_range<float>(args, permutation, row_start, row_end);
|
| 675 |
+
return;
|
| 676 |
+
case orbitquant::cpu::ScalarKind::Float16:
|
| 677 |
+
quantize_activation_range<c10::Half>(args, permutation, row_start, row_end);
|
| 678 |
+
return;
|
| 679 |
+
case orbitquant::cpu::ScalarKind::BFloat16:
|
| 680 |
+
quantize_activation_range<c10::BFloat16>(args, permutation, row_start, row_end);
|
| 681 |
+
return;
|
| 682 |
+
}
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
void quantize_activation_dispatch(
|
| 686 |
+
ActivationArgs const &args,
|
| 687 |
+
std::int64_t row_start,
|
| 688 |
+
std::int64_t row_end) {
|
| 689 |
+
if (args.permutation_i32 != nullptr) {
|
| 690 |
+
quantize_activation_dispatch_indexed(
|
| 691 |
+
args, args.permutation_i32, row_start, row_end);
|
| 692 |
+
return;
|
| 693 |
+
}
|
| 694 |
+
quantize_activation_dispatch_indexed(args, args.permutation, row_start, row_end);
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
void parallel_quantize_activations(ActivationArgs const &args) {
|
| 698 |
+
const std::int64_t values = args.rows * args.dim;
|
| 699 |
+
const int threads = values < 16'384
|
| 700 |
+
? 1
|
| 701 |
+
: std::max<int>(
|
| 702 |
+
1,
|
| 703 |
+
std::min<std::int64_t>(
|
| 704 |
+
orbitquant::cpu::requested_threads(),
|
| 705 |
+
args.rows));
|
| 706 |
+
if (threads == 1) {
|
| 707 |
+
quantize_activation_dispatch(args, 0, args.rows);
|
| 708 |
+
return;
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
std::vector<std::pair<std::int64_t, std::int64_t>> ranges;
|
| 712 |
+
ranges.reserve(threads);
|
| 713 |
+
const std::int64_t rows_per_thread = (args.rows + threads - 1) / threads;
|
| 714 |
+
for (int thread = 0; thread < threads; ++thread) {
|
| 715 |
+
const std::int64_t start = thread * rows_per_thread;
|
| 716 |
+
const std::int64_t end = std::min(args.rows, start + rows_per_thread);
|
| 717 |
+
if (start >= end) {
|
| 718 |
+
break;
|
| 719 |
+
}
|
| 720 |
+
ranges.emplace_back(start, end);
|
| 721 |
+
}
|
| 722 |
+
orbitquant::cpu::run_ranges(
|
| 723 |
+
ranges,
|
| 724 |
+
[&args](std::int64_t start, std::int64_t end) {
|
| 725 |
+
quantize_activation_dispatch(args, start, end);
|
| 726 |
+
});
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
} // namespace
|
| 730 |
+
|
| 731 |
+
void quantize_activations_cpu(
|
| 732 |
+
OrbitQuantTensor &out,
|
| 733 |
+
OrbitQuantTensor const &x,
|
| 734 |
+
OrbitQuantTensor const &permutation,
|
| 735 |
+
OrbitQuantTensor const &signs,
|
| 736 |
+
OrbitQuantTensor const ¢roids,
|
| 737 |
+
OrbitQuantTensor const &boundaries,
|
| 738 |
+
double eps,
|
| 739 |
+
double inv_sqrt_block,
|
| 740 |
+
int64_t block_size) {
|
| 741 |
+
using torch::headeronly::DeviceType;
|
| 742 |
+
using torch::headeronly::ScalarType;
|
| 743 |
+
|
| 744 |
+
STD_TORCH_CHECK(x.device().type() == DeviceType::CPU, "x must be a CPU tensor");
|
| 745 |
+
STD_TORCH_CHECK(out.device().type() == DeviceType::CPU, "out must be a CPU tensor");
|
| 746 |
+
STD_TORCH_CHECK(
|
| 747 |
+
permutation.device().type() == DeviceType::CPU,
|
| 748 |
+
"permutation must be a CPU tensor");
|
| 749 |
+
STD_TORCH_CHECK(signs.device().type() == DeviceType::CPU, "signs must be a CPU tensor");
|
| 750 |
+
STD_TORCH_CHECK(
|
| 751 |
+
centroids.device().type() == DeviceType::CPU,
|
| 752 |
+
"centroids must be a CPU tensor");
|
| 753 |
+
STD_TORCH_CHECK(
|
| 754 |
+
boundaries.device().type() == DeviceType::CPU,
|
| 755 |
+
"boundaries must be a CPU tensor");
|
| 756 |
+
STD_TORCH_CHECK(x.is_contiguous(), "x must be contiguous");
|
| 757 |
+
STD_TORCH_CHECK(out.is_contiguous(), "out must be contiguous");
|
| 758 |
+
STD_TORCH_CHECK(permutation.is_contiguous(), "permutation must be contiguous");
|
| 759 |
+
STD_TORCH_CHECK(signs.is_contiguous(), "signs must be contiguous");
|
| 760 |
+
STD_TORCH_CHECK(centroids.is_contiguous(), "centroids must be contiguous");
|
| 761 |
+
STD_TORCH_CHECK(boundaries.is_contiguous(), "boundaries must be contiguous");
|
| 762 |
+
STD_TORCH_CHECK(out.scalar_type() == x.scalar_type(), "out dtype must match x dtype");
|
| 763 |
+
STD_TORCH_CHECK(
|
| 764 |
+
permutation.scalar_type() == ScalarType::Long ||
|
| 765 |
+
permutation.scalar_type() == ScalarType::Int,
|
| 766 |
+
"permutation must be int32 or int64");
|
| 767 |
+
STD_TORCH_CHECK(signs.scalar_type() == ScalarType::Char, "signs must be int8");
|
| 768 |
+
STD_TORCH_CHECK(centroids.scalar_type() == ScalarType::Float, "centroids must be float32");
|
| 769 |
+
STD_TORCH_CHECK(boundaries.scalar_type() == ScalarType::Float, "boundaries must be float32");
|
| 770 |
+
STD_TORCH_CHECK(x.dim() == 2, "x must be rank 2");
|
| 771 |
+
STD_TORCH_CHECK(out.dim() == 2, "out must be rank 2");
|
| 772 |
+
STD_TORCH_CHECK(out.size(0) == x.size(0), "out row count must match x");
|
| 773 |
+
STD_TORCH_CHECK(out.size(1) == x.size(1), "out dimension must match x");
|
| 774 |
+
const int64_t dim = x.size(1);
|
| 775 |
+
STD_TORCH_CHECK(permutation.numel() == dim, "permutation must match the input dimension");
|
| 776 |
+
STD_TORCH_CHECK(signs.numel() == dim, "signs must match the input dimension");
|
| 777 |
+
STD_TORCH_CHECK(
|
| 778 |
+
centroids.numel() == boundaries.numel() + 1,
|
| 779 |
+
"centroids must contain exactly one more value than boundaries");
|
| 780 |
+
STD_TORCH_CHECK(centroids.numel() >= 2, "at least two centroids are required");
|
| 781 |
+
STD_TORCH_CHECK(block_size > 0, "block_size must be positive");
|
| 782 |
+
STD_TORCH_CHECK(
|
| 783 |
+
(block_size & (block_size - 1)) == 0,
|
| 784 |
+
"block_size must be a power of two");
|
| 785 |
+
STD_TORCH_CHECK(dim % block_size == 0, "block_size must divide the input dimension");
|
| 786 |
+
STD_TORCH_CHECK(eps >= 0.0, "eps must be non-negative");
|
| 787 |
+
STD_TORCH_CHECK(inv_sqrt_block > 0.0, "inv_sqrt_block must be positive");
|
| 788 |
+
|
| 789 |
+
const bool int32_permutation = permutation.scalar_type() == ScalarType::Int;
|
| 790 |
+
const std::int64_t *permutation_values =
|
| 791 |
+
int32_permutation ? nullptr : permutation.const_data_ptr<std::int64_t>();
|
| 792 |
+
const std::int32_t *permutation_values_i32 =
|
| 793 |
+
int32_permutation ? permutation.const_data_ptr<std::int32_t>() : nullptr;
|
| 794 |
+
const auto *sign_values = signs.const_data_ptr<std::int8_t>();
|
| 795 |
+
const auto permutation_at = [&](std::int64_t index) -> std::int64_t {
|
| 796 |
+
return int32_permutation
|
| 797 |
+
? static_cast<std::int64_t>(permutation_values_i32[index])
|
| 798 |
+
: permutation_values[index];
|
| 799 |
+
};
|
| 800 |
+
// The permutation/sign buffers are immutable module constants, so cache a
|
| 801 |
+
// fingerprint of validated buffers instead of re-scanning them per forward.
|
| 802 |
+
struct ValidatedEntry {
|
| 803 |
+
void const *permutation;
|
| 804 |
+
void const *signs;
|
| 805 |
+
std::int64_t dim;
|
| 806 |
+
std::int64_t head;
|
| 807 |
+
std::int64_t tail;
|
| 808 |
+
};
|
| 809 |
+
static std::mutex validated_mutex;
|
| 810 |
+
static std::array<ValidatedEntry, 16> validated_entries{};
|
| 811 |
+
static std::size_t validated_cursor = 0;
|
| 812 |
+
const ValidatedEntry candidate{
|
| 813 |
+
int32_permutation ? static_cast<void const *>(permutation_values_i32)
|
| 814 |
+
: static_cast<void const *>(permutation_values),
|
| 815 |
+
sign_values,
|
| 816 |
+
dim,
|
| 817 |
+
permutation_at(0),
|
| 818 |
+
permutation_at(dim - 1)};
|
| 819 |
+
bool already_validated = false;
|
| 820 |
+
{
|
| 821 |
+
std::lock_guard<std::mutex> lock(validated_mutex);
|
| 822 |
+
for (auto const &entry : validated_entries) {
|
| 823 |
+
if (entry.permutation == candidate.permutation &&
|
| 824 |
+
entry.signs == candidate.signs && entry.dim == candidate.dim &&
|
| 825 |
+
entry.head == candidate.head && entry.tail == candidate.tail) {
|
| 826 |
+
already_validated = true;
|
| 827 |
+
break;
|
| 828 |
+
}
|
| 829 |
+
}
|
| 830 |
+
}
|
| 831 |
+
if (!already_validated) {
|
| 832 |
+
for (int64_t index = 0; index < dim; ++index) {
|
| 833 |
+
const std::int64_t source_index = permutation_at(index);
|
| 834 |
+
STD_TORCH_CHECK(
|
| 835 |
+
source_index >= 0 && source_index < dim,
|
| 836 |
+
"permutation contains an out-of-range index");
|
| 837 |
+
STD_TORCH_CHECK(
|
| 838 |
+
sign_values[index] == -1 || sign_values[index] == 1,
|
| 839 |
+
"signs must contain only -1 and 1");
|
| 840 |
+
}
|
| 841 |
+
std::lock_guard<std::mutex> lock(validated_mutex);
|
| 842 |
+
validated_entries[validated_cursor % validated_entries.size()] = candidate;
|
| 843 |
+
++validated_cursor;
|
| 844 |
+
}
|
| 845 |
+
if (x.numel() == 0) {
|
| 846 |
+
return;
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
const ActivationArgs args{
|
| 850 |
+
out.mutable_data_ptr(),
|
| 851 |
+
x.const_data_ptr(),
|
| 852 |
+
permutation_values,
|
| 853 |
+
permutation_values_i32,
|
| 854 |
+
sign_values,
|
| 855 |
+
centroids.const_data_ptr<float>(),
|
| 856 |
+
boundaries.const_data_ptr<float>(),
|
| 857 |
+
activation_scalar_kind(x),
|
| 858 |
+
select_activation_isa(),
|
| 859 |
+
x.size(0),
|
| 860 |
+
dim,
|
| 861 |
+
boundaries.numel(),
|
| 862 |
+
block_size,
|
| 863 |
+
static_cast<float>(eps),
|
| 864 |
+
static_cast<float>(inv_sqrt_block),
|
| 865 |
+
};
|
| 866 |
+
parallel_quantize_activations(args);
|
| 867 |
+
}
|
orbitquant_packed_matmul_cuda/packed_matmul.cu
CHANGED
|
@@ -11,9 +11,34 @@
|
|
| 11 |
|
| 12 |
#include <algorithm>
|
| 13 |
#include <cstdint>
|
|
|
|
| 14 |
|
| 15 |
using namespace nvcuda;
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
__device__ __forceinline__ uint32_t unpack_lowbit_index(
|
| 18 |
uint8_t const *__restrict__ packed_weight_indices,
|
| 19 |
int64_t value_offset,
|
|
@@ -44,18 +69,943 @@ __device__ __forceinline__ uint32_t unpack_lowbit_index_const(
|
|
| 44 |
return (raw >> bit_offset) & mask;
|
| 45 |
}
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
template <int Bits>
|
| 48 |
__global__ void orbitquant_packed_matmul_wmma_bf16_kernel(
|
| 49 |
c10::BFloat16 *__restrict__ out,
|
| 50 |
c10::BFloat16 const *__restrict__ x,
|
| 51 |
uint8_t const *__restrict__ packed_weight_indices,
|
| 52 |
-
|
| 53 |
float const *__restrict__ centroids,
|
| 54 |
-
|
| 55 |
bool has_bias,
|
| 56 |
int64_t rows,
|
| 57 |
int64_t out_features,
|
| 58 |
int64_t in_features) {
|
|
|
|
| 59 |
constexpr int tile = 16;
|
| 60 |
constexpr int col_tiles = 4;
|
| 61 |
constexpr int warps_per_block = 8;
|
|
@@ -102,7 +1052,7 @@ __global__ void orbitquant_packed_matmul_wmma_bf16_kernel(
|
|
| 102 |
const int64_t value_offset = global_col * in_features + global_k;
|
| 103 |
const uint32_t index = unpack_lowbit_index_const<Bits>(
|
| 104 |
packed_weight_indices, value_offset);
|
| 105 |
-
value = row_norms[global_col] * centroids[index];
|
| 106 |
}
|
| 107 |
warp_w_tile[offset] = __float2bfloat16(value);
|
| 108 |
}
|
|
@@ -131,12 +1081,13 @@ __global__ void orbitquant_packed_matmul_wmma_bf16_kernel(
|
|
| 131 |
if (global_row < rows && global_col < out_features) {
|
| 132 |
float value = warp_acc_tile[offset];
|
| 133 |
if (has_bias) {
|
| 134 |
-
value += bias[global_col];
|
| 135 |
}
|
| 136 |
out[global_row * out_features + global_col] = static_cast<c10::BFloat16>(value);
|
| 137 |
}
|
| 138 |
}
|
| 139 |
}
|
|
|
|
| 140 |
}
|
| 141 |
|
| 142 |
template <int Bits>
|
|
@@ -144,9 +1095,9 @@ __global__ void orbitquant_packed_matmul_wmma_half_kernel(
|
|
| 144 |
c10::Half *__restrict__ out,
|
| 145 |
c10::Half const *__restrict__ x,
|
| 146 |
uint8_t const *__restrict__ packed_weight_indices,
|
| 147 |
-
|
| 148 |
float const *__restrict__ centroids,
|
| 149 |
-
|
| 150 |
bool has_bias,
|
| 151 |
int64_t rows,
|
| 152 |
int64_t out_features,
|
|
@@ -197,7 +1148,7 @@ __global__ void orbitquant_packed_matmul_wmma_half_kernel(
|
|
| 197 |
const int64_t value_offset = global_col * in_features + global_k;
|
| 198 |
const uint32_t index = unpack_lowbit_index_const<Bits>(
|
| 199 |
packed_weight_indices, value_offset);
|
| 200 |
-
value = row_norms[global_col] * centroids[index];
|
| 201 |
}
|
| 202 |
warp_w_tile[offset] = __float2half(value);
|
| 203 |
}
|
|
@@ -225,7 +1176,7 @@ __global__ void orbitquant_packed_matmul_wmma_half_kernel(
|
|
| 225 |
if (global_row < rows && global_col < out_features) {
|
| 226 |
float value = warp_acc_tile[offset];
|
| 227 |
if (has_bias) {
|
| 228 |
-
value += bias[global_col];
|
| 229 |
}
|
| 230 |
out[global_row * out_features + global_col] = static_cast<c10::Half>(value);
|
| 231 |
}
|
|
@@ -233,14 +1184,73 @@ __global__ void orbitquant_packed_matmul_wmma_half_kernel(
|
|
| 233 |
}
|
| 234 |
}
|
| 235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
template <typename scalar_t>
|
| 237 |
__global__ void orbitquant_packed_matmul_tiled_kernel(
|
| 238 |
scalar_t *__restrict__ out,
|
| 239 |
scalar_t const *__restrict__ x,
|
| 240 |
uint8_t const *__restrict__ packed_weight_indices,
|
| 241 |
-
|
| 242 |
float const *__restrict__ centroids,
|
| 243 |
-
|
| 244 |
bool has_bias,
|
| 245 |
int64_t rows,
|
| 246 |
int64_t out_features,
|
|
@@ -260,7 +1270,8 @@ __global__ void orbitquant_packed_matmul_tiled_kernel(
|
|
| 260 |
|
| 261 |
const uint32_t mask = (1u << bits) - 1u;
|
| 262 |
const bool output_valid = row < rows && col < out_features;
|
| 263 |
-
float acc =
|
|
|
|
| 264 |
|
| 265 |
for (int64_t k_start = 0; k_start < in_features; k_start += block_k) {
|
| 266 |
const int64_t x_tile_values = blockDim.y * block_k;
|
|
@@ -287,7 +1298,7 @@ __global__ void orbitquant_packed_matmul_tiled_kernel(
|
|
| 287 |
const int64_t value_offset = global_col * in_features + global_k;
|
| 288 |
const uint32_t index =
|
| 289 |
unpack_lowbit_index(packed_weight_indices, value_offset, bits, mask);
|
| 290 |
-
value = row_norms[global_col] * centroids[index];
|
| 291 |
}
|
| 292 |
w_tile[offset] = value;
|
| 293 |
}
|
|
@@ -331,7 +1342,9 @@ void matmul_packed_weight(
|
|
| 331 |
TORCH_CHECK(row_norms.is_contiguous(), "row norms must be contiguous");
|
| 332 |
TORCH_CHECK(centroids.is_contiguous(), "centroids must be contiguous");
|
| 333 |
TORCH_CHECK(packed_weight_indices.scalar_type() == torch::kUInt8, "packed weights must be uint8");
|
| 334 |
-
TORCH_CHECK(
|
|
|
|
|
|
|
| 335 |
TORCH_CHECK(centroids.scalar_type() == torch::kFloat, "centroids must be float32");
|
| 336 |
TORCH_CHECK(x.dim() == 2, "x must be rank 2");
|
| 337 |
TORCH_CHECK(out.dim() == 2, "out must be rank 2");
|
|
@@ -348,7 +1361,7 @@ void matmul_packed_weight(
|
|
| 348 |
if (has_bias) {
|
| 349 |
TORCH_CHECK(bias.device().is_cuda(), "bias must be a CUDA tensor");
|
| 350 |
TORCH_CHECK(bias.is_contiguous(), "bias must be contiguous");
|
| 351 |
-
TORCH_CHECK(bias.scalar_type() ==
|
| 352 |
TORCH_CHECK(bias.numel() == out_features, "bias must match out_features");
|
| 353 |
}
|
| 354 |
if (x.numel() == 0 || out_features == 0) {
|
|
@@ -356,8 +1369,9 @@ void matmul_packed_weight(
|
|
| 356 |
}
|
| 357 |
|
| 358 |
const int threads_n = static_cast<int>(std::min<int64_t>(std::max<int64_t>(block_n, 1), 64));
|
| 359 |
-
const int threads_m =
|
| 360 |
-
|
|
|
|
| 361 |
const int tile_k = static_cast<int>(std::min<int64_t>(std::max<int64_t>(block_k, 1), 128));
|
| 362 |
const dim3 block(threads_n, threads_m);
|
| 363 |
const dim3 grid(
|
|
@@ -367,8 +1381,136 @@ void matmul_packed_weight(
|
|
| 367 |
sizeof(float);
|
| 368 |
const at::cuda::OptionalCUDAGuard device_guard(device_of(x));
|
| 369 |
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
|
| 371 |
-
if (x.scalar_type() == at::kBFloat16)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
constexpr int tile = 16;
|
| 373 |
constexpr int col_tiles = 4;
|
| 374 |
constexpr int rows_per_block = tile * 8;
|
|
@@ -382,9 +1524,9 @@ void matmul_packed_weight(
|
|
| 382 |
reinterpret_cast<c10::BFloat16 *>(out.data_ptr()), \
|
| 383 |
reinterpret_cast<c10::BFloat16 const *>(x.data_ptr()), \
|
| 384 |
packed_weight_indices.data_ptr<uint8_t>(), \
|
| 385 |
-
row_norms.data_ptr<
|
| 386 |
centroids.data_ptr<float>(), \
|
| 387 |
-
has_bias ? bias.data_ptr<
|
| 388 |
has_bias, \
|
| 389 |
x.size(0), \
|
| 390 |
out_features, \
|
|
@@ -420,7 +1562,61 @@ void matmul_packed_weight(
|
|
| 420 |
return;
|
| 421 |
}
|
| 422 |
|
| 423 |
-
if (x.scalar_type() == at::kHalf)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
constexpr int tile = 16;
|
| 425 |
constexpr int col_tiles = 4;
|
| 426 |
constexpr int rows_per_block = tile * 8;
|
|
@@ -434,9 +1630,9 @@ void matmul_packed_weight(
|
|
| 434 |
reinterpret_cast<c10::Half *>(out.data_ptr()), \
|
| 435 |
reinterpret_cast<c10::Half const *>(x.data_ptr()), \
|
| 436 |
packed_weight_indices.data_ptr<uint8_t>(), \
|
| 437 |
-
row_norms.data_ptr<
|
| 438 |
centroids.data_ptr<float>(), \
|
| 439 |
-
has_bias ? bias.data_ptr<
|
| 440 |
has_bias, \
|
| 441 |
x.size(0), \
|
| 442 |
out_features, \
|
|
@@ -478,9 +1674,9 @@ void matmul_packed_weight(
|
|
| 478 |
out.data_ptr<scalar_t>(),
|
| 479 |
x.data_ptr<scalar_t>(),
|
| 480 |
packed_weight_indices.data_ptr<uint8_t>(),
|
| 481 |
-
row_norms.data_ptr<
|
| 482 |
centroids.data_ptr<float>(),
|
| 483 |
-
has_bias ? bias.data_ptr<
|
| 484 |
has_bias,
|
| 485 |
x.size(0),
|
| 486 |
out_features,
|
|
@@ -490,3 +1686,454 @@ void matmul_packed_weight(
|
|
| 490 |
});
|
| 491 |
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 492 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
#include <algorithm>
|
| 13 |
#include <cstdint>
|
| 14 |
+
#include <cstdlib>
|
| 15 |
|
| 16 |
using namespace nvcuda;
|
| 17 |
|
| 18 |
+
namespace {
|
| 19 |
+
|
| 20 |
+
// Escape hatch (and A/B benchmarking toggle) for the cp.async-pipelined mma64
|
| 21 |
+
// path: set ORBITQUANT_MMA64_DISABLE_PIPELINE=1 to force the legacy kernel.
|
| 22 |
+
inline bool orbitquant_mma64_pipeline_disabled() {
|
| 23 |
+
static const bool disabled = []() {
|
| 24 |
+
char const *value = std::getenv("ORBITQUANT_MMA64_DISABLE_PIPELINE");
|
| 25 |
+
return value != nullptr && value[0] != '\0' && value[0] != '0';
|
| 26 |
+
}();
|
| 27 |
+
return disabled;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
// ORBITQUANT_MMA64_FORCE_PIPELINE=1 extends the pipeline to every eligible
|
| 31 |
+
// width (W4/W6) for re-benchmarking on other GPUs.
|
| 32 |
+
inline bool orbitquant_mma64_pipeline_forced() {
|
| 33 |
+
static const bool forced = []() {
|
| 34 |
+
char const *value = std::getenv("ORBITQUANT_MMA64_FORCE_PIPELINE");
|
| 35 |
+
return value != nullptr && value[0] != '\0' && value[0] != '0';
|
| 36 |
+
}();
|
| 37 |
+
return forced;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
} // namespace
|
| 41 |
+
|
| 42 |
__device__ __forceinline__ uint32_t unpack_lowbit_index(
|
| 43 |
uint8_t const *__restrict__ packed_weight_indices,
|
| 44 |
int64_t value_offset,
|
|
|
|
| 69 |
return (raw >> bit_offset) & mask;
|
| 70 |
}
|
| 71 |
|
| 72 |
+
template <typename T>
|
| 73 |
+
__device__ __forceinline__ T orbitquant_mma_from_float(float value);
|
| 74 |
+
|
| 75 |
+
template <>
|
| 76 |
+
__device__ __forceinline__ half orbitquant_mma_from_float<half>(float value) {
|
| 77 |
+
return __float2half(value);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
template <>
|
| 81 |
+
__device__ __forceinline__ __nv_bfloat16 orbitquant_mma_from_float<__nv_bfloat16>(
|
| 82 |
+
float value) {
|
| 83 |
+
return __float2bfloat16(value);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
template <int Bits, int Values, typename mma_t>
|
| 87 |
+
__device__ __forceinline__ void decode_mma64_weight_segment(
|
| 88 |
+
mma_t *__restrict__ destination,
|
| 89 |
+
uint8_t const *__restrict__ packed_weight_indices,
|
| 90 |
+
c10::BFloat16 const *__restrict__ row_norms,
|
| 91 |
+
float const *__restrict__ centroids,
|
| 92 |
+
int64_t global_col,
|
| 93 |
+
int64_t global_k,
|
| 94 |
+
int64_t in_features,
|
| 95 |
+
bool valid_segment) {
|
| 96 |
+
constexpr uint32_t mask = (1u << Bits) - 1u;
|
| 97 |
+
constexpr int word_count = (Values * Bits + 31) / 32;
|
| 98 |
+
uint32_t packed_words[word_count] = {};
|
| 99 |
+
float norm = 0.0f;
|
| 100 |
+
if (valid_segment) {
|
| 101 |
+
const int64_t value_offset = global_col * in_features + global_k;
|
| 102 |
+
const int64_t byte_index = (value_offset * Bits) >> 3;
|
| 103 |
+
auto const *words = reinterpret_cast<uint32_t const *>(
|
| 104 |
+
packed_weight_indices + byte_index);
|
| 105 |
+
#pragma unroll
|
| 106 |
+
for (int word = 0; word < word_count; ++word) {
|
| 107 |
+
packed_words[word] = words[word];
|
| 108 |
+
}
|
| 109 |
+
norm = static_cast<float>(row_norms[global_col]);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
#pragma unroll
|
| 113 |
+
for (int index_offset = 0; index_offset < Values; ++index_offset) {
|
| 114 |
+
const int bit_start = index_offset * Bits;
|
| 115 |
+
const int word_index = bit_start >> 5;
|
| 116 |
+
const int shift = bit_start & 31;
|
| 117 |
+
uint32_t raw = packed_words[word_index] >> shift;
|
| 118 |
+
if (shift + Bits > 32) {
|
| 119 |
+
raw |= packed_words[word_index + 1] << (32 - shift);
|
| 120 |
+
}
|
| 121 |
+
const uint32_t codebook_index = raw & mask;
|
| 122 |
+
const float value = valid_segment ? norm * centroids[codebook_index] : 0.0f;
|
| 123 |
+
destination[index_offset] = orbitquant_mma_from_float<mma_t>(value);
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
template <int Bits, int Values, typename mma_t>
|
| 128 |
+
__device__ __forceinline__ void decode_mma64_weight_segment_from_stage(
|
| 129 |
+
mma_t *__restrict__ destination,
|
| 130 |
+
uint8_t const *__restrict__ staged_bytes,
|
| 131 |
+
float const *__restrict__ centroids,
|
| 132 |
+
float norm,
|
| 133 |
+
bool valid_segment) {
|
| 134 |
+
constexpr uint32_t mask = (1u << Bits) - 1u;
|
| 135 |
+
constexpr int word_count = (Values * Bits + 31) / 32;
|
| 136 |
+
uint32_t packed_words[word_count] = {};
|
| 137 |
+
if (valid_segment) {
|
| 138 |
+
auto const *words = reinterpret_cast<uint32_t const *>(staged_bytes);
|
| 139 |
+
#pragma unroll
|
| 140 |
+
for (int word = 0; word < word_count; ++word) {
|
| 141 |
+
packed_words[word] = words[word];
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
#pragma unroll
|
| 146 |
+
for (int index_offset = 0; index_offset < Values; ++index_offset) {
|
| 147 |
+
const int bit_start = index_offset * Bits;
|
| 148 |
+
const int word_index = bit_start >> 5;
|
| 149 |
+
const int shift = bit_start & 31;
|
| 150 |
+
uint32_t raw = packed_words[word_index] >> shift;
|
| 151 |
+
if (shift + Bits > 32) {
|
| 152 |
+
raw |= packed_words[word_index + 1] << (32 - shift);
|
| 153 |
+
}
|
| 154 |
+
const uint32_t codebook_index = raw & mask;
|
| 155 |
+
const float value = valid_segment ? norm * centroids[codebook_index] : 0.0f;
|
| 156 |
+
destination[index_offset] = orbitquant_mma_from_float<mma_t>(value);
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
// WMMA fragments (and the bf16 variants in particular) only exist on sm80+.
|
| 161 |
+
// Multi-architecture builds still instantiate these templates for older
|
| 162 |
+
// targets, so the bodies compile to empty stubs there; the host dispatch
|
| 163 |
+
// requires compute capability >= 8 before launching either kernel.
|
| 164 |
+
template <typename storage_t, typename mma_t, int Bits>
|
| 165 |
+
__global__ void orbitquant_packed_matmul_mma64_kernel(
|
| 166 |
+
storage_t *__restrict__ out,
|
| 167 |
+
storage_t const *__restrict__ x,
|
| 168 |
+
uint8_t const *__restrict__ packed_weight_indices,
|
| 169 |
+
c10::BFloat16 const *__restrict__ row_norms,
|
| 170 |
+
float const *__restrict__ centroids,
|
| 171 |
+
storage_t const *__restrict__ bias,
|
| 172 |
+
bool has_bias,
|
| 173 |
+
int64_t rows,
|
| 174 |
+
int64_t out_features,
|
| 175 |
+
int64_t in_features) {
|
| 176 |
+
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 800
|
| 177 |
+
constexpr int tile_m = 128;
|
| 178 |
+
constexpr int tile_n = 128;
|
| 179 |
+
constexpr int tile_k = 64;
|
| 180 |
+
constexpr int padded_k = 72;
|
| 181 |
+
constexpr int warps_per_block = 8;
|
| 182 |
+
constexpr int warp_tile = 16;
|
| 183 |
+
constexpr int col_tiles = tile_n / warp_tile;
|
| 184 |
+
constexpr int x_vector_values = 8;
|
| 185 |
+
constexpr int x_vectors_per_row = tile_k / x_vector_values;
|
| 186 |
+
constexpr int weight_segment_values = Bits == 3 ? 32 : 16;
|
| 187 |
+
constexpr int weight_segments_per_row = tile_k / weight_segment_values;
|
| 188 |
+
static_assert(sizeof(storage_t) == sizeof(mma_t));
|
| 189 |
+
|
| 190 |
+
__shared__ mma_t x_tile[tile_m * padded_k];
|
| 191 |
+
__shared__ mma_t weight_tile[tile_n * padded_k];
|
| 192 |
+
__shared__ float accumulator_tile[warps_per_block * warp_tile * warp_tile];
|
| 193 |
+
|
| 194 |
+
const int warp_id = threadIdx.x / warpSize;
|
| 195 |
+
const int lane = threadIdx.x & (warpSize - 1);
|
| 196 |
+
const int64_t block_row = int64_t(blockIdx.y) * tile_m;
|
| 197 |
+
const int64_t block_col = int64_t(blockIdx.x) * tile_n;
|
| 198 |
+
|
| 199 |
+
wmma::fragment<wmma::accumulator, warp_tile, warp_tile, warp_tile, float>
|
| 200 |
+
accumulators[col_tiles];
|
| 201 |
+
#pragma unroll
|
| 202 |
+
for (int col_tile = 0; col_tile < col_tiles; ++col_tile) {
|
| 203 |
+
wmma::fill_fragment(accumulators[col_tile], 0.0f);
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
for (int64_t k_start = 0; k_start < in_features; k_start += tile_k) {
|
| 207 |
+
constexpr int x_vector_tasks = tile_m * x_vectors_per_row;
|
| 208 |
+
for (int task = threadIdx.x; task < x_vector_tasks; task += blockDim.x) {
|
| 209 |
+
const int local_row = task / x_vectors_per_row;
|
| 210 |
+
const int local_vector = task - local_row * x_vectors_per_row;
|
| 211 |
+
const int local_k = local_vector * x_vector_values;
|
| 212 |
+
const int64_t global_row = block_row + local_row;
|
| 213 |
+
auto *destination = reinterpret_cast<uint4 *>(
|
| 214 |
+
x_tile + local_row * padded_k + local_k);
|
| 215 |
+
if (global_row < rows) {
|
| 216 |
+
auto const *source = reinterpret_cast<uint4 const *>(
|
| 217 |
+
x + global_row * in_features + k_start + local_k);
|
| 218 |
+
*destination = *source;
|
| 219 |
+
} else {
|
| 220 |
+
*destination = make_uint4(0, 0, 0, 0);
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
constexpr int weight_tasks = tile_n * weight_segments_per_row;
|
| 225 |
+
for (int weight_task = threadIdx.x; weight_task < weight_tasks;
|
| 226 |
+
weight_task += blockDim.x) {
|
| 227 |
+
const int local_col = weight_task / weight_segments_per_row;
|
| 228 |
+
const int local_segment =
|
| 229 |
+
weight_task - local_col * weight_segments_per_row;
|
| 230 |
+
const int local_k = local_segment * weight_segment_values;
|
| 231 |
+
const int64_t global_col = block_col + local_col;
|
| 232 |
+
decode_mma64_weight_segment<Bits, weight_segment_values>(
|
| 233 |
+
weight_tile + local_col * padded_k + local_k,
|
| 234 |
+
packed_weight_indices,
|
| 235 |
+
row_norms,
|
| 236 |
+
centroids,
|
| 237 |
+
global_col,
|
| 238 |
+
k_start + local_k,
|
| 239 |
+
in_features,
|
| 240 |
+
global_col < out_features);
|
| 241 |
+
}
|
| 242 |
+
__syncthreads();
|
| 243 |
+
|
| 244 |
+
#pragma unroll
|
| 245 |
+
for (int local_k = 0; local_k < tile_k; local_k += warp_tile) {
|
| 246 |
+
wmma::fragment<wmma::matrix_a, warp_tile, warp_tile, warp_tile, mma_t,
|
| 247 |
+
wmma::row_major>
|
| 248 |
+
lhs;
|
| 249 |
+
wmma::load_matrix_sync(
|
| 250 |
+
lhs,
|
| 251 |
+
x_tile + warp_id * warp_tile * padded_k + local_k,
|
| 252 |
+
padded_k);
|
| 253 |
+
#pragma unroll
|
| 254 |
+
for (int col_tile = 0; col_tile < col_tiles; ++col_tile) {
|
| 255 |
+
wmma::fragment<wmma::matrix_b, warp_tile, warp_tile, warp_tile, mma_t,
|
| 256 |
+
wmma::col_major>
|
| 257 |
+
rhs;
|
| 258 |
+
wmma::load_matrix_sync(
|
| 259 |
+
rhs,
|
| 260 |
+
weight_tile + col_tile * warp_tile * padded_k + local_k,
|
| 261 |
+
padded_k);
|
| 262 |
+
wmma::mma_sync(
|
| 263 |
+
accumulators[col_tile], lhs, rhs, accumulators[col_tile]);
|
| 264 |
+
}
|
| 265 |
+
}
|
| 266 |
+
__syncthreads();
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
float *warp_accumulator = accumulator_tile + warp_id * warp_tile * warp_tile;
|
| 270 |
+
#pragma unroll
|
| 271 |
+
for (int col_tile = 0; col_tile < col_tiles; ++col_tile) {
|
| 272 |
+
wmma::store_matrix_sync(
|
| 273 |
+
warp_accumulator,
|
| 274 |
+
accumulators[col_tile],
|
| 275 |
+
warp_tile,
|
| 276 |
+
wmma::mem_row_major);
|
| 277 |
+
__syncwarp();
|
| 278 |
+
for (int offset = lane; offset < warp_tile * warp_tile; offset += warpSize) {
|
| 279 |
+
const int local_row = offset / warp_tile;
|
| 280 |
+
const int local_col = offset - local_row * warp_tile;
|
| 281 |
+
const int64_t global_row = block_row + warp_id * warp_tile + local_row;
|
| 282 |
+
const int64_t global_col = block_col + col_tile * warp_tile + local_col;
|
| 283 |
+
if (global_row < rows && global_col < out_features) {
|
| 284 |
+
float value = warp_accumulator[offset];
|
| 285 |
+
if (has_bias) {
|
| 286 |
+
value += static_cast<float>(bias[global_col]);
|
| 287 |
+
}
|
| 288 |
+
out[global_row * out_features + global_col] =
|
| 289 |
+
static_cast<storage_t>(value);
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
__syncwarp();
|
| 293 |
+
}
|
| 294 |
+
#endif // __CUDA_ARCH__ >= 800
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
__device__ __forceinline__ void copy_async_16(
|
| 298 |
+
void *__restrict__ destination,
|
| 299 |
+
void const *__restrict__ source) {
|
| 300 |
+
#if __CUDA_ARCH__ >= 800
|
| 301 |
+
const uint32_t shared_address =
|
| 302 |
+
static_cast<uint32_t>(__cvta_generic_to_shared(destination));
|
| 303 |
+
asm volatile(
|
| 304 |
+
"cp.async.ca.shared.global [%0], [%1], 16;\n" : : "r"(shared_address),
|
| 305 |
+
"l"(source));
|
| 306 |
+
#else
|
| 307 |
+
*reinterpret_cast<uint4 *>(destination) =
|
| 308 |
+
*reinterpret_cast<uint4 const *>(source);
|
| 309 |
+
#endif
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
__device__ __forceinline__ void commit_async_copies() {
|
| 313 |
+
#if __CUDA_ARCH__ >= 800
|
| 314 |
+
asm volatile("cp.async.commit_group;\n" : :);
|
| 315 |
+
#endif
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
__device__ __forceinline__ void wait_for_async_copies() {
|
| 319 |
+
#if __CUDA_ARCH__ >= 800
|
| 320 |
+
asm volatile("cp.async.wait_group 0;\n" : :);
|
| 321 |
+
#endif
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
// cp.async-pipelined variant of the mma64 kernel (sm80+, byte-aligned k-tile
|
| 325 |
+
// strides, i.e. every supported width except W3). Interior blocks double-buffer
|
| 326 |
+
// the X tile and the packed weight bytes so global loads overlap the MMA work;
|
| 327 |
+
// edge blocks keep the guarded synchronous path.
|
| 328 |
+
template <typename storage_t, typename mma_t, int Bits>
|
| 329 |
+
__global__ void orbitquant_packed_matmul_mma64_pipelined_kernel(
|
| 330 |
+
storage_t *__restrict__ out,
|
| 331 |
+
storage_t const *__restrict__ x,
|
| 332 |
+
uint8_t const *__restrict__ packed_weight_indices,
|
| 333 |
+
c10::BFloat16 const *__restrict__ row_norms,
|
| 334 |
+
float const *__restrict__ centroids,
|
| 335 |
+
storage_t const *__restrict__ bias,
|
| 336 |
+
bool has_bias,
|
| 337 |
+
int64_t rows,
|
| 338 |
+
int64_t out_features,
|
| 339 |
+
int64_t in_features) {
|
| 340 |
+
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 800
|
| 341 |
+
constexpr int tile_m = 128;
|
| 342 |
+
constexpr int tile_n = 128;
|
| 343 |
+
constexpr int tile_k = 64;
|
| 344 |
+
constexpr int padded_k = 72;
|
| 345 |
+
constexpr int warps_per_block = 8;
|
| 346 |
+
constexpr int warp_tile = 16;
|
| 347 |
+
constexpr int col_tiles = tile_n / warp_tile;
|
| 348 |
+
constexpr int x_vector_values = 8;
|
| 349 |
+
constexpr int x_vectors_per_row = tile_k / x_vector_values;
|
| 350 |
+
constexpr int seg_stride = tile_k * Bits / 8;
|
| 351 |
+
constexpr int weight_segment_values = 16;
|
| 352 |
+
constexpr int weight_segments_per_row = tile_k / weight_segment_values;
|
| 353 |
+
constexpr int segment_bytes = weight_segment_values * Bits / 8;
|
| 354 |
+
static_assert(sizeof(storage_t) == sizeof(mma_t));
|
| 355 |
+
static_assert(Bits == 2 || Bits == 4 || Bits == 6);
|
| 356 |
+
|
| 357 |
+
extern __shared__ __align__(16) uint8_t dynamic_shared[];
|
| 358 |
+
mma_t *x_tiles = reinterpret_cast<mma_t *>(dynamic_shared);
|
| 359 |
+
mma_t *weight_tile = x_tiles + 2 * tile_m * padded_k;
|
| 360 |
+
float *accumulator_tile =
|
| 361 |
+
reinterpret_cast<float *>(weight_tile + tile_n * padded_k);
|
| 362 |
+
uint8_t *packed_stage = reinterpret_cast<uint8_t *>(
|
| 363 |
+
accumulator_tile + warps_per_block * warp_tile * warp_tile);
|
| 364 |
+
|
| 365 |
+
const int warp_id = threadIdx.x / warpSize;
|
| 366 |
+
const int lane = threadIdx.x & (warpSize - 1);
|
| 367 |
+
const int64_t block_row = int64_t(blockIdx.y) * tile_m;
|
| 368 |
+
const int64_t block_col = int64_t(blockIdx.x) * tile_n;
|
| 369 |
+
const int64_t packed_row_bytes = in_features * Bits / 8;
|
| 370 |
+
|
| 371 |
+
wmma::fragment<wmma::accumulator, warp_tile, warp_tile, warp_tile, float>
|
| 372 |
+
accumulators[col_tiles];
|
| 373 |
+
#pragma unroll
|
| 374 |
+
for (int col_tile = 0; col_tile < col_tiles; ++col_tile) {
|
| 375 |
+
wmma::fill_fragment(accumulators[col_tile], 0.0f);
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
const bool interior =
|
| 379 |
+
block_row + tile_m <= rows && block_col + tile_n <= out_features;
|
| 380 |
+
|
| 381 |
+
if (interior) {
|
| 382 |
+
constexpr int x_copies = tile_m * x_vectors_per_row;
|
| 383 |
+
constexpr int w_copies = tile_n * seg_stride / 16;
|
| 384 |
+
auto stage_tile = [&](int buffer, int64_t k_start) {
|
| 385 |
+
mma_t *x_destination = x_tiles + buffer * tile_m * padded_k;
|
| 386 |
+
for (int task = threadIdx.x; task < x_copies; task += blockDim.x) {
|
| 387 |
+
const int local_row = task / x_vectors_per_row;
|
| 388 |
+
const int local_vector = task - local_row * x_vectors_per_row;
|
| 389 |
+
copy_async_16(
|
| 390 |
+
x_destination + local_row * padded_k + local_vector * x_vector_values,
|
| 391 |
+
x + (block_row + local_row) * in_features + k_start +
|
| 392 |
+
local_vector * x_vector_values);
|
| 393 |
+
}
|
| 394 |
+
uint8_t *stage_destination = packed_stage + buffer * tile_n * seg_stride;
|
| 395 |
+
for (int task = threadIdx.x; task < w_copies; task += blockDim.x) {
|
| 396 |
+
const int byte_offset = task * 16;
|
| 397 |
+
const int local_col = byte_offset / seg_stride;
|
| 398 |
+
const int seg_byte = byte_offset - local_col * seg_stride;
|
| 399 |
+
copy_async_16(
|
| 400 |
+
stage_destination + byte_offset,
|
| 401 |
+
packed_weight_indices + (block_col + local_col) * packed_row_bytes +
|
| 402 |
+
(k_start * Bits) / 8 + seg_byte);
|
| 403 |
+
}
|
| 404 |
+
commit_async_copies();
|
| 405 |
+
};
|
| 406 |
+
|
| 407 |
+
stage_tile(0, 0);
|
| 408 |
+
wait_for_async_copies();
|
| 409 |
+
__syncthreads();
|
| 410 |
+
|
| 411 |
+
for (int64_t k_start = 0; k_start < in_features; k_start += tile_k) {
|
| 412 |
+
const int buffer = static_cast<int>((k_start / tile_k) & 1);
|
| 413 |
+
const bool has_next = k_start + tile_k < in_features;
|
| 414 |
+
if (has_next) {
|
| 415 |
+
stage_tile(buffer ^ 1, k_start + tile_k);
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
uint8_t const *stage_source = packed_stage + buffer * tile_n * seg_stride;
|
| 419 |
+
constexpr int weight_tasks = tile_n * weight_segments_per_row;
|
| 420 |
+
for (int task = threadIdx.x; task < weight_tasks; task += blockDim.x) {
|
| 421 |
+
const int local_col = task / weight_segments_per_row;
|
| 422 |
+
const int local_segment = task - local_col * weight_segments_per_row;
|
| 423 |
+
decode_mma64_weight_segment_from_stage<Bits, weight_segment_values>(
|
| 424 |
+
weight_tile + local_col * padded_k +
|
| 425 |
+
local_segment * weight_segment_values,
|
| 426 |
+
stage_source + local_col * seg_stride + local_segment * segment_bytes,
|
| 427 |
+
centroids,
|
| 428 |
+
static_cast<float>(row_norms[block_col + local_col]),
|
| 429 |
+
true);
|
| 430 |
+
}
|
| 431 |
+
__syncthreads();
|
| 432 |
+
|
| 433 |
+
mma_t const *x_source = x_tiles + buffer * tile_m * padded_k;
|
| 434 |
+
#pragma unroll
|
| 435 |
+
for (int local_k = 0; local_k < tile_k; local_k += warp_tile) {
|
| 436 |
+
wmma::fragment<wmma::matrix_a, warp_tile, warp_tile, warp_tile, mma_t,
|
| 437 |
+
wmma::row_major>
|
| 438 |
+
lhs;
|
| 439 |
+
wmma::load_matrix_sync(
|
| 440 |
+
lhs,
|
| 441 |
+
x_source + warp_id * warp_tile * padded_k + local_k,
|
| 442 |
+
padded_k);
|
| 443 |
+
#pragma unroll
|
| 444 |
+
for (int col_tile = 0; col_tile < col_tiles; ++col_tile) {
|
| 445 |
+
wmma::fragment<wmma::matrix_b, warp_tile, warp_tile, warp_tile, mma_t,
|
| 446 |
+
wmma::col_major>
|
| 447 |
+
rhs;
|
| 448 |
+
wmma::load_matrix_sync(
|
| 449 |
+
rhs,
|
| 450 |
+
weight_tile + col_tile * warp_tile * padded_k + local_k,
|
| 451 |
+
padded_k);
|
| 452 |
+
wmma::mma_sync(
|
| 453 |
+
accumulators[col_tile], lhs, rhs, accumulators[col_tile]);
|
| 454 |
+
}
|
| 455 |
+
}
|
| 456 |
+
__syncthreads();
|
| 457 |
+
if (has_next) {
|
| 458 |
+
wait_for_async_copies();
|
| 459 |
+
__syncthreads();
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
} else {
|
| 463 |
+
mma_t *x_tile = x_tiles;
|
| 464 |
+
for (int64_t k_start = 0; k_start < in_features; k_start += tile_k) {
|
| 465 |
+
constexpr int x_vector_tasks = tile_m * x_vectors_per_row;
|
| 466 |
+
for (int task = threadIdx.x; task < x_vector_tasks; task += blockDim.x) {
|
| 467 |
+
const int local_row = task / x_vectors_per_row;
|
| 468 |
+
const int local_vector = task - local_row * x_vectors_per_row;
|
| 469 |
+
const int local_k = local_vector * x_vector_values;
|
| 470 |
+
const int64_t global_row = block_row + local_row;
|
| 471 |
+
auto *destination = reinterpret_cast<uint4 *>(
|
| 472 |
+
x_tile + local_row * padded_k + local_k);
|
| 473 |
+
if (global_row < rows) {
|
| 474 |
+
auto const *source = reinterpret_cast<uint4 const *>(
|
| 475 |
+
x + global_row * in_features + k_start + local_k);
|
| 476 |
+
*destination = *source;
|
| 477 |
+
} else {
|
| 478 |
+
*destination = make_uint4(0, 0, 0, 0);
|
| 479 |
+
}
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
constexpr int weight_tasks = tile_n * weight_segments_per_row;
|
| 483 |
+
for (int weight_task = threadIdx.x; weight_task < weight_tasks;
|
| 484 |
+
weight_task += blockDim.x) {
|
| 485 |
+
const int local_col = weight_task / weight_segments_per_row;
|
| 486 |
+
const int local_segment =
|
| 487 |
+
weight_task - local_col * weight_segments_per_row;
|
| 488 |
+
const int local_k = local_segment * weight_segment_values;
|
| 489 |
+
const int64_t global_col = block_col + local_col;
|
| 490 |
+
decode_mma64_weight_segment<Bits, weight_segment_values>(
|
| 491 |
+
weight_tile + local_col * padded_k + local_k,
|
| 492 |
+
packed_weight_indices,
|
| 493 |
+
row_norms,
|
| 494 |
+
centroids,
|
| 495 |
+
global_col,
|
| 496 |
+
k_start + local_k,
|
| 497 |
+
in_features,
|
| 498 |
+
global_col < out_features);
|
| 499 |
+
}
|
| 500 |
+
__syncthreads();
|
| 501 |
+
|
| 502 |
+
#pragma unroll
|
| 503 |
+
for (int local_k = 0; local_k < tile_k; local_k += warp_tile) {
|
| 504 |
+
wmma::fragment<wmma::matrix_a, warp_tile, warp_tile, warp_tile, mma_t,
|
| 505 |
+
wmma::row_major>
|
| 506 |
+
lhs;
|
| 507 |
+
wmma::load_matrix_sync(
|
| 508 |
+
lhs,
|
| 509 |
+
x_tile + warp_id * warp_tile * padded_k + local_k,
|
| 510 |
+
padded_k);
|
| 511 |
+
#pragma unroll
|
| 512 |
+
for (int col_tile = 0; col_tile < col_tiles; ++col_tile) {
|
| 513 |
+
wmma::fragment<wmma::matrix_b, warp_tile, warp_tile, warp_tile, mma_t,
|
| 514 |
+
wmma::col_major>
|
| 515 |
+
rhs;
|
| 516 |
+
wmma::load_matrix_sync(
|
| 517 |
+
rhs,
|
| 518 |
+
weight_tile + col_tile * warp_tile * padded_k + local_k,
|
| 519 |
+
padded_k);
|
| 520 |
+
wmma::mma_sync(
|
| 521 |
+
accumulators[col_tile], lhs, rhs, accumulators[col_tile]);
|
| 522 |
+
}
|
| 523 |
+
}
|
| 524 |
+
__syncthreads();
|
| 525 |
+
}
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
float *warp_accumulator = accumulator_tile + warp_id * warp_tile * warp_tile;
|
| 529 |
+
#pragma unroll
|
| 530 |
+
for (int col_tile = 0; col_tile < col_tiles; ++col_tile) {
|
| 531 |
+
wmma::store_matrix_sync(
|
| 532 |
+
warp_accumulator,
|
| 533 |
+
accumulators[col_tile],
|
| 534 |
+
warp_tile,
|
| 535 |
+
wmma::mem_row_major);
|
| 536 |
+
__syncwarp();
|
| 537 |
+
for (int offset = lane; offset < warp_tile * warp_tile; offset += warpSize) {
|
| 538 |
+
const int local_row = offset / warp_tile;
|
| 539 |
+
const int local_col = offset - local_row * warp_tile;
|
| 540 |
+
const int64_t global_row = block_row + warp_id * warp_tile + local_row;
|
| 541 |
+
const int64_t global_col = block_col + col_tile * warp_tile + local_col;
|
| 542 |
+
if (global_row < rows && global_col < out_features) {
|
| 543 |
+
float value = warp_accumulator[offset];
|
| 544 |
+
if (has_bias) {
|
| 545 |
+
value += static_cast<float>(bias[global_col]);
|
| 546 |
+
}
|
| 547 |
+
out[global_row * out_features + global_col] =
|
| 548 |
+
static_cast<storage_t>(value);
|
| 549 |
+
}
|
| 550 |
+
}
|
| 551 |
+
__syncwarp();
|
| 552 |
+
}
|
| 553 |
+
#endif // __CUDA_ARCH__ >= 800
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
__device__ __forceinline__ uint8_t orbitquant_bucketize_w4(
|
| 557 |
+
float value,
|
| 558 |
+
float const *__restrict__ boundaries) {
|
| 559 |
+
int index = value > boundaries[7] ? 8 : 0;
|
| 560 |
+
index += value > boundaries[index + 3] ? 4 : 0;
|
| 561 |
+
index += value > boundaries[index + 1] ? 2 : 0;
|
| 562 |
+
index += value > boundaries[index] ? 1 : 0;
|
| 563 |
+
return static_cast<uint8_t>(index);
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
template <typename storage_t, typename index_t, int Dim>
|
| 567 |
+
__global__ void orbitquant_rpbh_quantize_pack_w4_kernel(
|
| 568 |
+
uint8_t *__restrict__ packed_out,
|
| 569 |
+
float *__restrict__ norms_out,
|
| 570 |
+
storage_t const *__restrict__ x,
|
| 571 |
+
index_t const *__restrict__ permutation,
|
| 572 |
+
int8_t const *__restrict__ signs,
|
| 573 |
+
float const *__restrict__ boundaries,
|
| 574 |
+
float eps,
|
| 575 |
+
float inv_sqrt_block,
|
| 576 |
+
int64_t rows) {
|
| 577 |
+
extern __shared__ __align__(16) float shared[];
|
| 578 |
+
float *values = shared;
|
| 579 |
+
float *reduction = values + Dim;
|
| 580 |
+
float *boundary_table = reduction + blockDim.x;
|
| 581 |
+
const int tid = threadIdx.x;
|
| 582 |
+
const int64_t row = blockIdx.x;
|
| 583 |
+
|
| 584 |
+
float squared_sum = 0.0f;
|
| 585 |
+
for (int col = tid; col < Dim; col += blockDim.x) {
|
| 586 |
+
const int64_t source_col = permutation[col];
|
| 587 |
+
const float value =
|
| 588 |
+
static_cast<float>(x[row * Dim + source_col]) *
|
| 589 |
+
static_cast<float>(signs[col]);
|
| 590 |
+
values[col] = value;
|
| 591 |
+
squared_sum = fmaf(value, value, squared_sum);
|
| 592 |
+
}
|
| 593 |
+
reduction[tid] = squared_sum;
|
| 594 |
+
if (tid < 15) {
|
| 595 |
+
boundary_table[tid] = boundaries[tid];
|
| 596 |
+
}
|
| 597 |
+
__syncthreads();
|
| 598 |
+
|
| 599 |
+
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {
|
| 600 |
+
if (tid < stride) {
|
| 601 |
+
reduction[tid] += reduction[tid + stride];
|
| 602 |
+
}
|
| 603 |
+
__syncthreads();
|
| 604 |
+
}
|
| 605 |
+
const float norm = sqrtf(reduction[0]);
|
| 606 |
+
if (tid == 0) {
|
| 607 |
+
norms_out[row] = norm;
|
| 608 |
+
}
|
| 609 |
+
const float inv_norm = 1.0f / (norm + eps);
|
| 610 |
+
for (int col = tid; col < Dim; col += blockDim.x) {
|
| 611 |
+
values[col] *= inv_norm;
|
| 612 |
+
}
|
| 613 |
+
__syncthreads();
|
| 614 |
+
|
| 615 |
+
#pragma unroll
|
| 616 |
+
for (int butterfly_width = 1; butterfly_width < Dim;
|
| 617 |
+
butterfly_width <<= 1) {
|
| 618 |
+
constexpr int butterflies = Dim / 2;
|
| 619 |
+
for (int butterfly = tid; butterfly < butterflies;
|
| 620 |
+
butterfly += blockDim.x) {
|
| 621 |
+
const int group = butterfly / butterfly_width;
|
| 622 |
+
const int offset = butterfly - group * butterfly_width;
|
| 623 |
+
const int left = group * (butterfly_width * 2) + offset;
|
| 624 |
+
const int right = left + butterfly_width;
|
| 625 |
+
const float lhs = values[left];
|
| 626 |
+
const float rhs = values[right];
|
| 627 |
+
values[left] = lhs + rhs;
|
| 628 |
+
values[right] = lhs - rhs;
|
| 629 |
+
}
|
| 630 |
+
__syncthreads();
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
constexpr int packed_dim = Dim / 2;
|
| 634 |
+
for (int byte_col = tid; byte_col < packed_dim; byte_col += blockDim.x) {
|
| 635 |
+
const float low_value = values[byte_col * 2] * inv_sqrt_block;
|
| 636 |
+
const float high_value = values[byte_col * 2 + 1] * inv_sqrt_block;
|
| 637 |
+
const uint8_t low = orbitquant_bucketize_w4(low_value, boundary_table);
|
| 638 |
+
const uint8_t high = orbitquant_bucketize_w4(high_value, boundary_table);
|
| 639 |
+
packed_out[row * packed_dim + byte_col] =
|
| 640 |
+
static_cast<uint8_t>(low | (high << 4));
|
| 641 |
+
}
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
template <typename storage_t, typename index_t, int Dim, int OrbitBlock>
|
| 645 |
+
__global__ void orbitquant_rpbh_quantize_int8_kernel(
|
| 646 |
+
int8_t *__restrict__ int8_out,
|
| 647 |
+
float *__restrict__ norms_out,
|
| 648 |
+
storage_t const *__restrict__ x,
|
| 649 |
+
index_t const *__restrict__ permutation,
|
| 650 |
+
int8_t const *__restrict__ signs,
|
| 651 |
+
float const *__restrict__ boundaries,
|
| 652 |
+
int8_t const *__restrict__ codes,
|
| 653 |
+
float eps,
|
| 654 |
+
float inv_sqrt_block,
|
| 655 |
+
int64_t rows) {
|
| 656 |
+
static_assert(Dim % OrbitBlock == 0);
|
| 657 |
+
extern __shared__ __align__(16) float shared[];
|
| 658 |
+
float *values = shared;
|
| 659 |
+
float *reduction = values + Dim;
|
| 660 |
+
float *boundary_table = reduction + blockDim.x;
|
| 661 |
+
int8_t *code_table = reinterpret_cast<int8_t *>(boundary_table + 15);
|
| 662 |
+
const int tid = threadIdx.x;
|
| 663 |
+
const int64_t row = blockIdx.x;
|
| 664 |
+
|
| 665 |
+
float squared_sum = 0.0f;
|
| 666 |
+
for (int col = tid; col < Dim; col += blockDim.x) {
|
| 667 |
+
const int64_t source_col = permutation[col];
|
| 668 |
+
const float value =
|
| 669 |
+
static_cast<float>(x[row * Dim + source_col]) *
|
| 670 |
+
static_cast<float>(signs[col]);
|
| 671 |
+
values[col] = value;
|
| 672 |
+
squared_sum = fmaf(value, value, squared_sum);
|
| 673 |
+
}
|
| 674 |
+
reduction[tid] = squared_sum;
|
| 675 |
+
if (tid < 15) {
|
| 676 |
+
boundary_table[tid] = boundaries[tid];
|
| 677 |
+
}
|
| 678 |
+
if (tid < 16) {
|
| 679 |
+
code_table[tid] = codes[tid];
|
| 680 |
+
}
|
| 681 |
+
__syncthreads();
|
| 682 |
+
|
| 683 |
+
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {
|
| 684 |
+
if (tid < stride) {
|
| 685 |
+
reduction[tid] += reduction[tid + stride];
|
| 686 |
+
}
|
| 687 |
+
__syncthreads();
|
| 688 |
+
}
|
| 689 |
+
const float norm = sqrtf(reduction[0]);
|
| 690 |
+
if (tid == 0) {
|
| 691 |
+
norms_out[row] = norm;
|
| 692 |
+
}
|
| 693 |
+
const float inv_norm = 1.0f / (norm + eps);
|
| 694 |
+
for (int col = tid; col < Dim; col += blockDim.x) {
|
| 695 |
+
values[col] *= inv_norm;
|
| 696 |
+
}
|
| 697 |
+
__syncthreads();
|
| 698 |
+
|
| 699 |
+
#pragma unroll
|
| 700 |
+
for (int butterfly_width = 1; butterfly_width < OrbitBlock;
|
| 701 |
+
butterfly_width <<= 1) {
|
| 702 |
+
constexpr int butterflies = Dim / 2;
|
| 703 |
+
constexpr int butterflies_per_block = OrbitBlock / 2;
|
| 704 |
+
for (int butterfly = tid; butterfly < butterflies;
|
| 705 |
+
butterfly += blockDim.x) {
|
| 706 |
+
const int orbit_block = butterfly / butterflies_per_block;
|
| 707 |
+
const int local_butterfly =
|
| 708 |
+
butterfly - orbit_block * butterflies_per_block;
|
| 709 |
+
const int group = local_butterfly / butterfly_width;
|
| 710 |
+
const int offset = local_butterfly - group * butterfly_width;
|
| 711 |
+
const int left = orbit_block * OrbitBlock +
|
| 712 |
+
group * (butterfly_width * 2) + offset;
|
| 713 |
+
const int right = left + butterfly_width;
|
| 714 |
+
const float lhs = values[left];
|
| 715 |
+
const float rhs = values[right];
|
| 716 |
+
values[left] = lhs + rhs;
|
| 717 |
+
values[right] = lhs - rhs;
|
| 718 |
+
}
|
| 719 |
+
__syncthreads();
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
for (int col = tid; col < Dim; col += blockDim.x) {
|
| 723 |
+
const float value = values[col] * inv_sqrt_block;
|
| 724 |
+
const uint8_t index = orbitquant_bucketize_w4(value, boundary_table);
|
| 725 |
+
int8_out[row * Dim + col] = code_table[index];
|
| 726 |
+
}
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
template <
|
| 730 |
+
typename storage_t,
|
| 731 |
+
int TileM,
|
| 732 |
+
int TileN,
|
| 733 |
+
bool AsyncPacked,
|
| 734 |
+
bool KMajorWeight>
|
| 735 |
+
__global__ void orbitquant_packed_w4a4_int8_mma_kernel(
|
| 736 |
+
storage_t *__restrict__ out,
|
| 737 |
+
uint8_t const *__restrict__ packed_activations,
|
| 738 |
+
uint8_t const *__restrict__ packed_weight_indices,
|
| 739 |
+
float const *__restrict__ token_norms,
|
| 740 |
+
c10::BFloat16 const *__restrict__ row_norms,
|
| 741 |
+
int8_t const *__restrict__ activation_codes,
|
| 742 |
+
int8_t const *__restrict__ weight_codes,
|
| 743 |
+
storage_t const *__restrict__ bias,
|
| 744 |
+
bool has_bias,
|
| 745 |
+
float activation_scale,
|
| 746 |
+
float weight_scale,
|
| 747 |
+
int64_t rows,
|
| 748 |
+
int64_t out_features,
|
| 749 |
+
int64_t in_features) {
|
| 750 |
+
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 800
|
| 751 |
+
constexpr int tile_k = 64;
|
| 752 |
+
constexpr int packed_tile_k = tile_k / 2;
|
| 753 |
+
constexpr int padded_k = 80;
|
| 754 |
+
constexpr int warp_tile = 16;
|
| 755 |
+
constexpr int warp_rows = TileM / warp_tile;
|
| 756 |
+
constexpr int col_tiles_per_warp = 8;
|
| 757 |
+
constexpr int warp_col_groups = TileN / (col_tiles_per_warp * warp_tile);
|
| 758 |
+
constexpr int warps_per_block = warp_rows * warp_col_groups;
|
| 759 |
+
static_assert(TileM == 128 || TileM == 256);
|
| 760 |
+
static_assert(TileN == 128 || TileN == 256);
|
| 761 |
+
static_assert(warps_per_block == 8 || warps_per_block == 16);
|
| 762 |
+
|
| 763 |
+
extern __shared__ __align__(16) uint8_t shared_memory[];
|
| 764 |
+
int8_t *activation_tile = reinterpret_cast<int8_t *>(shared_memory);
|
| 765 |
+
int8_t *weight_tile = activation_tile + TileM * padded_k;
|
| 766 |
+
int32_t *accumulator_tile = reinterpret_cast<int32_t *>(
|
| 767 |
+
weight_tile + TileN * padded_k);
|
| 768 |
+
int8_t *code_tables = reinterpret_cast<int8_t *>(
|
| 769 |
+
accumulator_tile + warps_per_block * warp_tile * warp_tile);
|
| 770 |
+
uint8_t *packed_activation_stage =
|
| 771 |
+
reinterpret_cast<uint8_t *>(code_tables + 32);
|
| 772 |
+
uint8_t *packed_weight_stage =
|
| 773 |
+
packed_activation_stage + (AsyncPacked ? TileM * packed_tile_k : 0);
|
| 774 |
+
|
| 775 |
+
const int warp_id = threadIdx.x / warpSize;
|
| 776 |
+
const int lane = threadIdx.x & (warpSize - 1);
|
| 777 |
+
const int warp_row = warp_id % warp_rows;
|
| 778 |
+
const int warp_col_group = warp_id / warp_rows;
|
| 779 |
+
const int64_t block_row = int64_t(blockIdx.y) * TileM;
|
| 780 |
+
const int64_t block_col = int64_t(blockIdx.x) * TileN;
|
| 781 |
+
const int64_t packed_row_stride = in_features / 2;
|
| 782 |
+
|
| 783 |
+
if (threadIdx.x < 16) {
|
| 784 |
+
code_tables[threadIdx.x] = activation_codes[threadIdx.x];
|
| 785 |
+
code_tables[16 + threadIdx.x] = weight_codes[threadIdx.x];
|
| 786 |
+
}
|
| 787 |
+
|
| 788 |
+
wmma::fragment<wmma::accumulator, warp_tile, warp_tile, warp_tile, int>
|
| 789 |
+
accumulators[col_tiles_per_warp];
|
| 790 |
+
#pragma unroll
|
| 791 |
+
for (int col_tile = 0; col_tile < col_tiles_per_warp; ++col_tile) {
|
| 792 |
+
wmma::fill_fragment(accumulators[col_tile], 0);
|
| 793 |
+
}
|
| 794 |
+
__syncthreads();
|
| 795 |
+
|
| 796 |
+
const bool use_async =
|
| 797 |
+
AsyncPacked && block_row + TileM <= rows &&
|
| 798 |
+
block_col + TileN <= out_features && out_features % 16 == 0;
|
| 799 |
+
if constexpr (AsyncPacked) {
|
| 800 |
+
if (use_async) {
|
| 801 |
+
constexpr int activation_vectors = TileM * packed_tile_k / 16;
|
| 802 |
+
for (int vector = threadIdx.x; vector < activation_vectors;
|
| 803 |
+
vector += blockDim.x) {
|
| 804 |
+
const int byte_offset = vector * 16;
|
| 805 |
+
const int local_row = byte_offset / packed_tile_k;
|
| 806 |
+
const int local_k_byte = byte_offset - local_row * packed_tile_k;
|
| 807 |
+
copy_async_16(
|
| 808 |
+
packed_activation_stage + byte_offset,
|
| 809 |
+
packed_activations +
|
| 810 |
+
(block_row + local_row) * packed_row_stride + local_k_byte);
|
| 811 |
+
}
|
| 812 |
+
constexpr int weight_vectors = packed_tile_k * TileN / 16;
|
| 813 |
+
for (int vector = threadIdx.x; vector < weight_vectors;
|
| 814 |
+
vector += blockDim.x) {
|
| 815 |
+
const int byte_offset = vector * 16;
|
| 816 |
+
if constexpr (KMajorWeight) {
|
| 817 |
+
const int local_k_byte = byte_offset / TileN;
|
| 818 |
+
const int local_col = byte_offset - local_k_byte * TileN;
|
| 819 |
+
copy_async_16(
|
| 820 |
+
packed_weight_stage + byte_offset,
|
| 821 |
+
packed_weight_indices + local_k_byte * out_features + block_col +
|
| 822 |
+
local_col);
|
| 823 |
+
} else {
|
| 824 |
+
const int local_col = byte_offset / packed_tile_k;
|
| 825 |
+
const int local_k_byte = byte_offset - local_col * packed_tile_k;
|
| 826 |
+
copy_async_16(
|
| 827 |
+
packed_weight_stage + byte_offset,
|
| 828 |
+
packed_weight_indices +
|
| 829 |
+
(block_col + local_col) * packed_row_stride + local_k_byte);
|
| 830 |
+
}
|
| 831 |
+
}
|
| 832 |
+
commit_async_copies();
|
| 833 |
+
wait_for_async_copies();
|
| 834 |
+
__syncthreads();
|
| 835 |
+
}
|
| 836 |
+
}
|
| 837 |
+
|
| 838 |
+
for (int64_t k_start = 0; k_start < in_features; k_start += tile_k) {
|
| 839 |
+
constexpr int activation_tasks = TileM * packed_tile_k;
|
| 840 |
+
for (int task = threadIdx.x; task < activation_tasks; task += blockDim.x) {
|
| 841 |
+
const int local_row = task / packed_tile_k;
|
| 842 |
+
const int local_k_byte = task - local_row * packed_tile_k;
|
| 843 |
+
const int64_t global_row = block_row + local_row;
|
| 844 |
+
uint8_t packed = 0;
|
| 845 |
+
if (use_async) {
|
| 846 |
+
packed = packed_activation_stage[task];
|
| 847 |
+
} else if (global_row < rows) {
|
| 848 |
+
packed = packed_activations[
|
| 849 |
+
global_row * packed_row_stride + k_start / 2 + local_k_byte];
|
| 850 |
+
}
|
| 851 |
+
const int destination = local_row * padded_k + local_k_byte * 2;
|
| 852 |
+
activation_tile[destination] = code_tables[packed & 15u];
|
| 853 |
+
activation_tile[destination + 1] = code_tables[packed >> 4];
|
| 854 |
+
}
|
| 855 |
+
|
| 856 |
+
constexpr int weight_tasks = packed_tile_k * TileN;
|
| 857 |
+
for (int task = threadIdx.x; task < weight_tasks; task += blockDim.x) {
|
| 858 |
+
const int local_k_byte = task / TileN;
|
| 859 |
+
const int local_col = task - local_k_byte * TileN;
|
| 860 |
+
const int64_t global_col = block_col + local_col;
|
| 861 |
+
uint8_t packed = 0;
|
| 862 |
+
if (use_async) {
|
| 863 |
+
if constexpr (KMajorWeight) {
|
| 864 |
+
packed = packed_weight_stage[task];
|
| 865 |
+
} else {
|
| 866 |
+
packed = packed_weight_stage[local_col * packed_tile_k + local_k_byte];
|
| 867 |
+
}
|
| 868 |
+
} else if (global_col < out_features) {
|
| 869 |
+
if constexpr (KMajorWeight) {
|
| 870 |
+
packed = packed_weight_indices[
|
| 871 |
+
(k_start / 2 + local_k_byte) * out_features + global_col];
|
| 872 |
+
} else {
|
| 873 |
+
packed = packed_weight_indices[
|
| 874 |
+
global_col * packed_row_stride + k_start / 2 + local_k_byte];
|
| 875 |
+
}
|
| 876 |
+
}
|
| 877 |
+
const int destination = local_col * padded_k + local_k_byte * 2;
|
| 878 |
+
weight_tile[destination] = code_tables[16 + (packed & 15u)];
|
| 879 |
+
weight_tile[destination + 1] = code_tables[16 + (packed >> 4)];
|
| 880 |
+
}
|
| 881 |
+
__syncthreads();
|
| 882 |
+
|
| 883 |
+
const bool has_next_tile = k_start + tile_k < in_features;
|
| 884 |
+
if constexpr (AsyncPacked) {
|
| 885 |
+
if (use_async && has_next_tile) {
|
| 886 |
+
const int64_t next_k_byte = (k_start + tile_k) / 2;
|
| 887 |
+
constexpr int activation_vectors = TileM * packed_tile_k / 16;
|
| 888 |
+
for (int vector = threadIdx.x; vector < activation_vectors;
|
| 889 |
+
vector += blockDim.x) {
|
| 890 |
+
const int byte_offset = vector * 16;
|
| 891 |
+
const int local_row = byte_offset / packed_tile_k;
|
| 892 |
+
const int local_k_byte = byte_offset - local_row * packed_tile_k;
|
| 893 |
+
copy_async_16(
|
| 894 |
+
packed_activation_stage + byte_offset,
|
| 895 |
+
packed_activations +
|
| 896 |
+
(block_row + local_row) * packed_row_stride + next_k_byte +
|
| 897 |
+
local_k_byte);
|
| 898 |
+
}
|
| 899 |
+
constexpr int weight_vectors = packed_tile_k * TileN / 16;
|
| 900 |
+
for (int vector = threadIdx.x; vector < weight_vectors;
|
| 901 |
+
vector += blockDim.x) {
|
| 902 |
+
const int byte_offset = vector * 16;
|
| 903 |
+
if constexpr (KMajorWeight) {
|
| 904 |
+
const int local_k_byte = byte_offset / TileN;
|
| 905 |
+
const int local_col = byte_offset - local_k_byte * TileN;
|
| 906 |
+
copy_async_16(
|
| 907 |
+
packed_weight_stage + byte_offset,
|
| 908 |
+
packed_weight_indices +
|
| 909 |
+
(next_k_byte + local_k_byte) * out_features + block_col +
|
| 910 |
+
local_col);
|
| 911 |
+
} else {
|
| 912 |
+
const int local_col = byte_offset / packed_tile_k;
|
| 913 |
+
const int local_k_byte = byte_offset - local_col * packed_tile_k;
|
| 914 |
+
copy_async_16(
|
| 915 |
+
packed_weight_stage + byte_offset,
|
| 916 |
+
packed_weight_indices +
|
| 917 |
+
(block_col + local_col) * packed_row_stride + next_k_byte +
|
| 918 |
+
local_k_byte);
|
| 919 |
+
}
|
| 920 |
+
}
|
| 921 |
+
commit_async_copies();
|
| 922 |
+
}
|
| 923 |
+
}
|
| 924 |
+
|
| 925 |
+
#pragma unroll
|
| 926 |
+
for (int local_k = 0; local_k < tile_k; local_k += warp_tile) {
|
| 927 |
+
wmma::fragment<wmma::matrix_a, warp_tile, warp_tile, warp_tile, signed char,
|
| 928 |
+
wmma::row_major>
|
| 929 |
+
lhs;
|
| 930 |
+
wmma::load_matrix_sync(
|
| 931 |
+
lhs,
|
| 932 |
+
reinterpret_cast<signed char const *>(
|
| 933 |
+
activation_tile + warp_row * warp_tile * padded_k + local_k),
|
| 934 |
+
padded_k);
|
| 935 |
+
#pragma unroll
|
| 936 |
+
for (int col_tile = 0; col_tile < col_tiles_per_warp; ++col_tile) {
|
| 937 |
+
wmma::fragment<wmma::matrix_b, warp_tile, warp_tile, warp_tile, signed char,
|
| 938 |
+
wmma::col_major>
|
| 939 |
+
rhs;
|
| 940 |
+
wmma::load_matrix_sync(
|
| 941 |
+
rhs,
|
| 942 |
+
reinterpret_cast<signed char const *>(
|
| 943 |
+
weight_tile +
|
| 944 |
+
(warp_col_group * col_tiles_per_warp + col_tile) * warp_tile *
|
| 945 |
+
padded_k +
|
| 946 |
+
local_k),
|
| 947 |
+
padded_k);
|
| 948 |
+
wmma::mma_sync(
|
| 949 |
+
accumulators[col_tile], lhs, rhs, accumulators[col_tile]);
|
| 950 |
+
}
|
| 951 |
+
}
|
| 952 |
+
__syncthreads();
|
| 953 |
+
if constexpr (AsyncPacked) {
|
| 954 |
+
if (use_async && has_next_tile) {
|
| 955 |
+
wait_for_async_copies();
|
| 956 |
+
__syncthreads();
|
| 957 |
+
}
|
| 958 |
+
}
|
| 959 |
+
}
|
| 960 |
+
|
| 961 |
+
int32_t *warp_accumulator =
|
| 962 |
+
accumulator_tile + warp_id * warp_tile * warp_tile;
|
| 963 |
+
const float surrogate_scale = activation_scale * weight_scale;
|
| 964 |
+
#pragma unroll
|
| 965 |
+
for (int col_tile = 0; col_tile < col_tiles_per_warp; ++col_tile) {
|
| 966 |
+
wmma::store_matrix_sync(
|
| 967 |
+
warp_accumulator,
|
| 968 |
+
accumulators[col_tile],
|
| 969 |
+
warp_tile,
|
| 970 |
+
wmma::mem_row_major);
|
| 971 |
+
__syncwarp();
|
| 972 |
+
for (int offset = lane; offset < warp_tile * warp_tile; offset += warpSize) {
|
| 973 |
+
const int local_row = offset / warp_tile;
|
| 974 |
+
const int local_col = offset - local_row * warp_tile;
|
| 975 |
+
const int64_t global_row = block_row + warp_row * warp_tile + local_row;
|
| 976 |
+
const int64_t global_col =
|
| 977 |
+
block_col +
|
| 978 |
+
(warp_col_group * col_tiles_per_warp + col_tile) * warp_tile +
|
| 979 |
+
local_col;
|
| 980 |
+
if (global_row < rows && global_col < out_features) {
|
| 981 |
+
float value = static_cast<float>(warp_accumulator[offset]);
|
| 982 |
+
value *= token_norms[global_row] *
|
| 983 |
+
static_cast<float>(row_norms[global_col]) * surrogate_scale;
|
| 984 |
+
if (has_bias) {
|
| 985 |
+
value += static_cast<float>(bias[global_col]);
|
| 986 |
+
}
|
| 987 |
+
out[global_row * out_features + global_col] =
|
| 988 |
+
static_cast<storage_t>(value);
|
| 989 |
+
}
|
| 990 |
+
}
|
| 991 |
+
__syncwarp();
|
| 992 |
+
}
|
| 993 |
+
#endif // __CUDA_ARCH__ >= 800
|
| 994 |
+
}
|
| 995 |
+
|
| 996 |
template <int Bits>
|
| 997 |
__global__ void orbitquant_packed_matmul_wmma_bf16_kernel(
|
| 998 |
c10::BFloat16 *__restrict__ out,
|
| 999 |
c10::BFloat16 const *__restrict__ x,
|
| 1000 |
uint8_t const *__restrict__ packed_weight_indices,
|
| 1001 |
+
c10::BFloat16 const *__restrict__ row_norms,
|
| 1002 |
float const *__restrict__ centroids,
|
| 1003 |
+
c10::BFloat16 const *__restrict__ bias,
|
| 1004 |
bool has_bias,
|
| 1005 |
int64_t rows,
|
| 1006 |
int64_t out_features,
|
| 1007 |
int64_t in_features) {
|
| 1008 |
+
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 800
|
| 1009 |
constexpr int tile = 16;
|
| 1010 |
constexpr int col_tiles = 4;
|
| 1011 |
constexpr int warps_per_block = 8;
|
|
|
|
| 1052 |
const int64_t value_offset = global_col * in_features + global_k;
|
| 1053 |
const uint32_t index = unpack_lowbit_index_const<Bits>(
|
| 1054 |
packed_weight_indices, value_offset);
|
| 1055 |
+
value = static_cast<float>(row_norms[global_col]) * centroids[index];
|
| 1056 |
}
|
| 1057 |
warp_w_tile[offset] = __float2bfloat16(value);
|
| 1058 |
}
|
|
|
|
| 1081 |
if (global_row < rows && global_col < out_features) {
|
| 1082 |
float value = warp_acc_tile[offset];
|
| 1083 |
if (has_bias) {
|
| 1084 |
+
value += static_cast<float>(bias[global_col]);
|
| 1085 |
}
|
| 1086 |
out[global_row * out_features + global_col] = static_cast<c10::BFloat16>(value);
|
| 1087 |
}
|
| 1088 |
}
|
| 1089 |
}
|
| 1090 |
+
#endif // __CUDA_ARCH__ >= 800
|
| 1091 |
}
|
| 1092 |
|
| 1093 |
template <int Bits>
|
|
|
|
| 1095 |
c10::Half *__restrict__ out,
|
| 1096 |
c10::Half const *__restrict__ x,
|
| 1097 |
uint8_t const *__restrict__ packed_weight_indices,
|
| 1098 |
+
c10::BFloat16 const *__restrict__ row_norms,
|
| 1099 |
float const *__restrict__ centroids,
|
| 1100 |
+
c10::Half const *__restrict__ bias,
|
| 1101 |
bool has_bias,
|
| 1102 |
int64_t rows,
|
| 1103 |
int64_t out_features,
|
|
|
|
| 1148 |
const int64_t value_offset = global_col * in_features + global_k;
|
| 1149 |
const uint32_t index = unpack_lowbit_index_const<Bits>(
|
| 1150 |
packed_weight_indices, value_offset);
|
| 1151 |
+
value = static_cast<float>(row_norms[global_col]) * centroids[index];
|
| 1152 |
}
|
| 1153 |
warp_w_tile[offset] = __float2half(value);
|
| 1154 |
}
|
|
|
|
| 1176 |
if (global_row < rows && global_col < out_features) {
|
| 1177 |
float value = warp_acc_tile[offset];
|
| 1178 |
if (has_bias) {
|
| 1179 |
+
value += static_cast<float>(bias[global_col]);
|
| 1180 |
}
|
| 1181 |
out[global_row * out_features + global_col] = static_cast<c10::Half>(value);
|
| 1182 |
}
|
|
|
|
| 1184 |
}
|
| 1185 |
}
|
| 1186 |
|
| 1187 |
+
template <typename scalar_t>
|
| 1188 |
+
__global__ void orbitquant_packed_matmul_small_rows_kernel(
|
| 1189 |
+
scalar_t *__restrict__ out,
|
| 1190 |
+
scalar_t const *__restrict__ x,
|
| 1191 |
+
uint8_t const *__restrict__ packed_weight_indices,
|
| 1192 |
+
c10::BFloat16 const *__restrict__ row_norms,
|
| 1193 |
+
float const *__restrict__ centroids,
|
| 1194 |
+
scalar_t const *__restrict__ bias,
|
| 1195 |
+
bool has_bias,
|
| 1196 |
+
int64_t rows,
|
| 1197 |
+
int64_t out_features,
|
| 1198 |
+
int64_t in_features,
|
| 1199 |
+
int64_t bits) {
|
| 1200 |
+
constexpr int channels_per_warp = 4;
|
| 1201 |
+
const int lane = threadIdx.x;
|
| 1202 |
+
const int64_t row = blockIdx.y;
|
| 1203 |
+
const int64_t col_start = int64_t(blockIdx.x) * channels_per_warp;
|
| 1204 |
+
const uint32_t mask = (1u << bits) - 1u;
|
| 1205 |
+
float accumulators[channels_per_warp] = {};
|
| 1206 |
+
float norms[channels_per_warp];
|
| 1207 |
+
|
| 1208 |
+
#pragma unroll
|
| 1209 |
+
for (int col_offset = 0; col_offset < channels_per_warp; ++col_offset) {
|
| 1210 |
+
const int64_t col = col_start + col_offset;
|
| 1211 |
+
norms[col_offset] =
|
| 1212 |
+
col < out_features ? static_cast<float>(row_norms[col]) : 0.0f;
|
| 1213 |
+
}
|
| 1214 |
+
|
| 1215 |
+
for (int64_t k = lane; k < in_features; k += warpSize) {
|
| 1216 |
+
const float x_value = static_cast<float>(x[row * in_features + k]);
|
| 1217 |
+
#pragma unroll
|
| 1218 |
+
for (int col_offset = 0; col_offset < channels_per_warp; ++col_offset) {
|
| 1219 |
+
const int64_t col = col_start + col_offset;
|
| 1220 |
+
if (col < out_features) {
|
| 1221 |
+
const int64_t value_offset = col * in_features + k;
|
| 1222 |
+
const uint32_t index =
|
| 1223 |
+
unpack_lowbit_index(packed_weight_indices, value_offset, bits, mask);
|
| 1224 |
+
accumulators[col_offset] += x_value * norms[col_offset] * centroids[index];
|
| 1225 |
+
}
|
| 1226 |
+
}
|
| 1227 |
+
}
|
| 1228 |
+
|
| 1229 |
+
#pragma unroll
|
| 1230 |
+
for (int col_offset = 0; col_offset < channels_per_warp; ++col_offset) {
|
| 1231 |
+
#pragma unroll
|
| 1232 |
+
for (int offset = warpSize / 2; offset > 0; offset >>= 1) {
|
| 1233 |
+
accumulators[col_offset] +=
|
| 1234 |
+
__shfl_down_sync(0xffffffffu, accumulators[col_offset], offset);
|
| 1235 |
+
}
|
| 1236 |
+
const int64_t col = col_start + col_offset;
|
| 1237 |
+
if (lane == 0 && col < out_features) {
|
| 1238 |
+
const float value =
|
| 1239 |
+
accumulators[col_offset] +
|
| 1240 |
+
(has_bias ? static_cast<float>(bias[col]) : 0.0f);
|
| 1241 |
+
out[row * out_features + col] = static_cast<scalar_t>(value);
|
| 1242 |
+
}
|
| 1243 |
+
}
|
| 1244 |
+
}
|
| 1245 |
+
|
| 1246 |
template <typename scalar_t>
|
| 1247 |
__global__ void orbitquant_packed_matmul_tiled_kernel(
|
| 1248 |
scalar_t *__restrict__ out,
|
| 1249 |
scalar_t const *__restrict__ x,
|
| 1250 |
uint8_t const *__restrict__ packed_weight_indices,
|
| 1251 |
+
c10::BFloat16 const *__restrict__ row_norms,
|
| 1252 |
float const *__restrict__ centroids,
|
| 1253 |
+
scalar_t const *__restrict__ bias,
|
| 1254 |
bool has_bias,
|
| 1255 |
int64_t rows,
|
| 1256 |
int64_t out_features,
|
|
|
|
| 1270 |
|
| 1271 |
const uint32_t mask = (1u << bits) - 1u;
|
| 1272 |
const bool output_valid = row < rows && col < out_features;
|
| 1273 |
+
float acc =
|
| 1274 |
+
output_valid && has_bias ? static_cast<float>(bias[col]) : 0.0f;
|
| 1275 |
|
| 1276 |
for (int64_t k_start = 0; k_start < in_features; k_start += block_k) {
|
| 1277 |
const int64_t x_tile_values = blockDim.y * block_k;
|
|
|
|
| 1298 |
const int64_t value_offset = global_col * in_features + global_k;
|
| 1299 |
const uint32_t index =
|
| 1300 |
unpack_lowbit_index(packed_weight_indices, value_offset, bits, mask);
|
| 1301 |
+
value = static_cast<float>(row_norms[global_col]) * centroids[index];
|
| 1302 |
}
|
| 1303 |
w_tile[offset] = value;
|
| 1304 |
}
|
|
|
|
| 1342 |
TORCH_CHECK(row_norms.is_contiguous(), "row norms must be contiguous");
|
| 1343 |
TORCH_CHECK(centroids.is_contiguous(), "centroids must be contiguous");
|
| 1344 |
TORCH_CHECK(packed_weight_indices.scalar_type() == torch::kUInt8, "packed weights must be uint8");
|
| 1345 |
+
TORCH_CHECK(
|
| 1346 |
+
row_norms.scalar_type() == torch::kBFloat16,
|
| 1347 |
+
"CUDA row_norms must be bfloat16");
|
| 1348 |
TORCH_CHECK(centroids.scalar_type() == torch::kFloat, "centroids must be float32");
|
| 1349 |
TORCH_CHECK(x.dim() == 2, "x must be rank 2");
|
| 1350 |
TORCH_CHECK(out.dim() == 2, "out must be rank 2");
|
|
|
|
| 1361 |
if (has_bias) {
|
| 1362 |
TORCH_CHECK(bias.device().is_cuda(), "bias must be a CUDA tensor");
|
| 1363 |
TORCH_CHECK(bias.is_contiguous(), "bias must be contiguous");
|
| 1364 |
+
TORCH_CHECK(bias.scalar_type() == x.scalar_type(), "CUDA bias dtype must match x");
|
| 1365 |
TORCH_CHECK(bias.numel() == out_features, "bias must match out_features");
|
| 1366 |
}
|
| 1367 |
if (x.numel() == 0 || out_features == 0) {
|
|
|
|
| 1369 |
}
|
| 1370 |
|
| 1371 |
const int threads_n = static_cast<int>(std::min<int64_t>(std::max<int64_t>(block_n, 1), 64));
|
| 1372 |
+
const int threads_m = static_cast<int>(std::min<int64_t>(
|
| 1373 |
+
x.size(0),
|
| 1374 |
+
std::min<int64_t>(std::max<int64_t>(block_m, 1), 1024 / threads_n)));
|
| 1375 |
const int tile_k = static_cast<int>(std::min<int64_t>(std::max<int64_t>(block_k, 1), 128));
|
| 1376 |
const dim3 block(threads_n, threads_m);
|
| 1377 |
const dim3 grid(
|
|
|
|
| 1381 |
sizeof(float);
|
| 1382 |
const at::cuda::OptionalCUDAGuard device_guard(device_of(x));
|
| 1383 |
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
| 1384 |
+
const cudaDeviceProp *mma_properties = at::cuda::getCurrentDeviceProperties();
|
| 1385 |
+
|
| 1386 |
+
if (x.size(0) <= 8) {
|
| 1387 |
+
constexpr int channels_per_warp = 4;
|
| 1388 |
+
const dim3 small_rows_block(32);
|
| 1389 |
+
const dim3 small_rows_grid(
|
| 1390 |
+
(out_features + channels_per_warp - 1) / channels_per_warp,
|
| 1391 |
+
x.size(0));
|
| 1392 |
+
AT_DISPATCH_FLOATING_TYPES_AND2(
|
| 1393 |
+
at::kHalf, at::kBFloat16, x.scalar_type(),
|
| 1394 |
+
"orbitquant_packed_matmul_cuda_small_rows", [&] {
|
| 1395 |
+
orbitquant_packed_matmul_small_rows_kernel<scalar_t>
|
| 1396 |
+
<<<small_rows_grid, small_rows_block, 0, stream>>>(
|
| 1397 |
+
out.data_ptr<scalar_t>(),
|
| 1398 |
+
x.data_ptr<scalar_t>(),
|
| 1399 |
+
packed_weight_indices.data_ptr<uint8_t>(),
|
| 1400 |
+
row_norms.data_ptr<c10::BFloat16>(),
|
| 1401 |
+
centroids.data_ptr<float>(),
|
| 1402 |
+
has_bias ? bias.data_ptr<scalar_t>() : nullptr,
|
| 1403 |
+
has_bias,
|
| 1404 |
+
x.size(0),
|
| 1405 |
+
out_features,
|
| 1406 |
+
in_features,
|
| 1407 |
+
bits);
|
| 1408 |
+
});
|
| 1409 |
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 1410 |
+
return;
|
| 1411 |
+
}
|
| 1412 |
|
| 1413 |
+
if (x.scalar_type() == at::kBFloat16 && x.size(0) >= 9 &&
|
| 1414 |
+
mma_properties->major >= 8) {
|
| 1415 |
+
if (in_features % 64 == 0 &&
|
| 1416 |
+
(bits == 2 || bits == 3 || bits == 4 || bits == 6)) {
|
| 1417 |
+
constexpr int mma_tile_m = 128;
|
| 1418 |
+
constexpr int mma_tile_n = 128;
|
| 1419 |
+
const dim3 mma_block(256);
|
| 1420 |
+
const dim3 mma_grid(
|
| 1421 |
+
(out_features + mma_tile_n - 1) / mma_tile_n,
|
| 1422 |
+
(x.size(0) + mma_tile_m - 1) / mma_tile_m);
|
| 1423 |
+
|
| 1424 |
+
#define ORBITQUANT_LAUNCH_MMA64_PIPELINED(STORAGE_TYPE, MMA_TYPE, BITS_VALUE) \
|
| 1425 |
+
do { \
|
| 1426 |
+
constexpr int kSegStride = 64 * (BITS_VALUE) / 8; \
|
| 1427 |
+
const int pipelined_shared_bytes = static_cast<int>( \
|
| 1428 |
+
2 * 128 * 72 * sizeof(MMA_TYPE) + 128 * 72 * sizeof(MMA_TYPE) + \
|
| 1429 |
+
8 * 16 * 16 * sizeof(float) + 2 * 128 * kSegStride); \
|
| 1430 |
+
if (pipelined_shared_bytes > mma_properties->sharedMemPerBlock) { \
|
| 1431 |
+
C10_CUDA_CHECK(cudaFuncSetAttribute( \
|
| 1432 |
+
orbitquant_packed_matmul_mma64_pipelined_kernel<STORAGE_TYPE, \
|
| 1433 |
+
MMA_TYPE, \
|
| 1434 |
+
BITS_VALUE>, \
|
| 1435 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize, \
|
| 1436 |
+
pipelined_shared_bytes)); \
|
| 1437 |
+
} \
|
| 1438 |
+
orbitquant_packed_matmul_mma64_pipelined_kernel<STORAGE_TYPE, MMA_TYPE, \
|
| 1439 |
+
BITS_VALUE> \
|
| 1440 |
+
<<<mma_grid, mma_block, pipelined_shared_bytes, stream>>>( \
|
| 1441 |
+
reinterpret_cast<STORAGE_TYPE *>(out.data_ptr()), \
|
| 1442 |
+
reinterpret_cast<STORAGE_TYPE const *>(x.data_ptr()), \
|
| 1443 |
+
packed_weight_indices.data_ptr<uint8_t>(), \
|
| 1444 |
+
row_norms.data_ptr<c10::BFloat16>(), \
|
| 1445 |
+
centroids.data_ptr<float>(), \
|
| 1446 |
+
has_bias ? bias.data_ptr<STORAGE_TYPE>() : nullptr, \
|
| 1447 |
+
has_bias, \
|
| 1448 |
+
x.size(0), \
|
| 1449 |
+
out_features, \
|
| 1450 |
+
in_features); \
|
| 1451 |
+
} while (0)
|
| 1452 |
+
// The cp.async pipeline wins when the launch is latency-bound: with cold L2
|
| 1453 |
+
// (each layer's weights are evicted between calls in a real model) it is
|
| 1454 |
+
// 1.35-1.46x faster for W2/W4/W6 on RTX 4060 Ti, A40, and RTX 4090 whenever
|
| 1455 |
+
// the grid fits in one wave (blocks <= SM count), and it regresses up to 15%
|
| 1456 |
+
// once the grid oversubscribes the device. Gate on grid size; measured
|
| 1457 |
+
// 2026-07 across the three architectures above.
|
| 1458 |
+
// ORBITQUANT_MMA64_FORCE_PIPELINE=1 bypasses the grid gate,
|
| 1459 |
+
// ORBITQUANT_MMA64_DISABLE_PIPELINE=1 forces the legacy kernel everywhere.
|
| 1460 |
+
#define ORBITQUANT_MMA64_USE_PIPELINE(BITS_VALUE) \
|
| 1461 |
+
((orbitquant_mma64_pipeline_forced() || \
|
| 1462 |
+
static_cast<int64_t>(mma_grid.x) * mma_grid.y <= \
|
| 1463 |
+
mma_properties->multiProcessorCount) && \
|
| 1464 |
+
!orbitquant_mma64_pipeline_disabled() && \
|
| 1465 |
+
mma_properties->major >= 8 && \
|
| 1466 |
+
(2 * 128 * 72 * 2 + 128 * 72 * 2 + 8 * 16 * 16 * 4 + \
|
| 1467 |
+
2 * 128 * (64 * (BITS_VALUE) / 8)) <= \
|
| 1468 |
+
static_cast<int>(mma_properties->sharedMemPerBlockOptin))
|
| 1469 |
+
|
| 1470 |
+
#define ORBITQUANT_LAUNCH_MMA64_BF16(BITS_VALUE) \
|
| 1471 |
+
orbitquant_packed_matmul_mma64_kernel<c10::BFloat16, __nv_bfloat16, \
|
| 1472 |
+
BITS_VALUE><<<mma_grid, mma_block, 0, \
|
| 1473 |
+
stream>>>( \
|
| 1474 |
+
reinterpret_cast<c10::BFloat16 *>(out.data_ptr()), \
|
| 1475 |
+
reinterpret_cast<c10::BFloat16 const *>(x.data_ptr()), \
|
| 1476 |
+
packed_weight_indices.data_ptr<uint8_t>(), \
|
| 1477 |
+
row_norms.data_ptr<c10::BFloat16>(), \
|
| 1478 |
+
centroids.data_ptr<float>(), \
|
| 1479 |
+
has_bias ? bias.data_ptr<c10::BFloat16>() : nullptr, \
|
| 1480 |
+
has_bias, \
|
| 1481 |
+
x.size(0), \
|
| 1482 |
+
out_features, \
|
| 1483 |
+
in_features)
|
| 1484 |
+
switch (bits) {
|
| 1485 |
+
case 2:
|
| 1486 |
+
if (ORBITQUANT_MMA64_USE_PIPELINE(2)) {
|
| 1487 |
+
ORBITQUANT_LAUNCH_MMA64_PIPELINED(c10::BFloat16, __nv_bfloat16, 2);
|
| 1488 |
+
} else {
|
| 1489 |
+
ORBITQUANT_LAUNCH_MMA64_BF16(2);
|
| 1490 |
+
}
|
| 1491 |
+
break;
|
| 1492 |
+
case 3:
|
| 1493 |
+
ORBITQUANT_LAUNCH_MMA64_BF16(3);
|
| 1494 |
+
break;
|
| 1495 |
+
case 4:
|
| 1496 |
+
if (ORBITQUANT_MMA64_USE_PIPELINE(4)) {
|
| 1497 |
+
ORBITQUANT_LAUNCH_MMA64_PIPELINED(c10::BFloat16, __nv_bfloat16, 4);
|
| 1498 |
+
} else {
|
| 1499 |
+
ORBITQUANT_LAUNCH_MMA64_BF16(4);
|
| 1500 |
+
}
|
| 1501 |
+
break;
|
| 1502 |
+
case 6:
|
| 1503 |
+
if (ORBITQUANT_MMA64_USE_PIPELINE(6)) {
|
| 1504 |
+
ORBITQUANT_LAUNCH_MMA64_PIPELINED(c10::BFloat16, __nv_bfloat16, 6);
|
| 1505 |
+
} else {
|
| 1506 |
+
ORBITQUANT_LAUNCH_MMA64_BF16(6);
|
| 1507 |
+
}
|
| 1508 |
+
break;
|
| 1509 |
+
}
|
| 1510 |
+
#undef ORBITQUANT_LAUNCH_MMA64_BF16
|
| 1511 |
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 1512 |
+
return;
|
| 1513 |
+
}
|
| 1514 |
constexpr int tile = 16;
|
| 1515 |
constexpr int col_tiles = 4;
|
| 1516 |
constexpr int rows_per_block = tile * 8;
|
|
|
|
| 1524 |
reinterpret_cast<c10::BFloat16 *>(out.data_ptr()), \
|
| 1525 |
reinterpret_cast<c10::BFloat16 const *>(x.data_ptr()), \
|
| 1526 |
packed_weight_indices.data_ptr<uint8_t>(), \
|
| 1527 |
+
row_norms.data_ptr<c10::BFloat16>(), \
|
| 1528 |
centroids.data_ptr<float>(), \
|
| 1529 |
+
has_bias ? bias.data_ptr<c10::BFloat16>() : nullptr, \
|
| 1530 |
has_bias, \
|
| 1531 |
x.size(0), \
|
| 1532 |
out_features, \
|
|
|
|
| 1562 |
return;
|
| 1563 |
}
|
| 1564 |
|
| 1565 |
+
if (x.scalar_type() == at::kHalf && x.size(0) >= 9 &&
|
| 1566 |
+
mma_properties->major >= 8) {
|
| 1567 |
+
if (in_features % 64 == 0 &&
|
| 1568 |
+
(bits == 2 || bits == 3 || bits == 4 || bits == 6)) {
|
| 1569 |
+
constexpr int mma_tile_m = 128;
|
| 1570 |
+
constexpr int mma_tile_n = 128;
|
| 1571 |
+
const dim3 mma_block(256);
|
| 1572 |
+
const dim3 mma_grid(
|
| 1573 |
+
(out_features + mma_tile_n - 1) / mma_tile_n,
|
| 1574 |
+
(x.size(0) + mma_tile_m - 1) / mma_tile_m);
|
| 1575 |
+
#define ORBITQUANT_LAUNCH_MMA64_HALF(BITS_VALUE) \
|
| 1576 |
+
orbitquant_packed_matmul_mma64_kernel<c10::Half, half, BITS_VALUE> \
|
| 1577 |
+
<<<mma_grid, mma_block, 0, stream>>>( \
|
| 1578 |
+
reinterpret_cast<c10::Half *>(out.data_ptr()), \
|
| 1579 |
+
reinterpret_cast<c10::Half const *>(x.data_ptr()), \
|
| 1580 |
+
packed_weight_indices.data_ptr<uint8_t>(), \
|
| 1581 |
+
row_norms.data_ptr<c10::BFloat16>(), \
|
| 1582 |
+
centroids.data_ptr<float>(), \
|
| 1583 |
+
has_bias ? bias.data_ptr<c10::Half>() : nullptr, \
|
| 1584 |
+
has_bias, \
|
| 1585 |
+
x.size(0), \
|
| 1586 |
+
out_features, \
|
| 1587 |
+
in_features)
|
| 1588 |
+
switch (bits) {
|
| 1589 |
+
case 2:
|
| 1590 |
+
if (ORBITQUANT_MMA64_USE_PIPELINE(2)) {
|
| 1591 |
+
ORBITQUANT_LAUNCH_MMA64_PIPELINED(c10::Half, half, 2);
|
| 1592 |
+
} else {
|
| 1593 |
+
ORBITQUANT_LAUNCH_MMA64_HALF(2);
|
| 1594 |
+
}
|
| 1595 |
+
break;
|
| 1596 |
+
case 3:
|
| 1597 |
+
ORBITQUANT_LAUNCH_MMA64_HALF(3);
|
| 1598 |
+
break;
|
| 1599 |
+
case 4:
|
| 1600 |
+
if (ORBITQUANT_MMA64_USE_PIPELINE(4)) {
|
| 1601 |
+
ORBITQUANT_LAUNCH_MMA64_PIPELINED(c10::Half, half, 4);
|
| 1602 |
+
} else {
|
| 1603 |
+
ORBITQUANT_LAUNCH_MMA64_HALF(4);
|
| 1604 |
+
}
|
| 1605 |
+
break;
|
| 1606 |
+
case 6:
|
| 1607 |
+
if (ORBITQUANT_MMA64_USE_PIPELINE(6)) {
|
| 1608 |
+
ORBITQUANT_LAUNCH_MMA64_PIPELINED(c10::Half, half, 6);
|
| 1609 |
+
} else {
|
| 1610 |
+
ORBITQUANT_LAUNCH_MMA64_HALF(6);
|
| 1611 |
+
}
|
| 1612 |
+
break;
|
| 1613 |
+
}
|
| 1614 |
+
#undef ORBITQUANT_LAUNCH_MMA64_HALF
|
| 1615 |
+
#undef ORBITQUANT_MMA64_USE_PIPELINE
|
| 1616 |
+
#undef ORBITQUANT_LAUNCH_MMA64_PIPELINED
|
| 1617 |
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 1618 |
+
return;
|
| 1619 |
+
}
|
| 1620 |
constexpr int tile = 16;
|
| 1621 |
constexpr int col_tiles = 4;
|
| 1622 |
constexpr int rows_per_block = tile * 8;
|
|
|
|
| 1630 |
reinterpret_cast<c10::Half *>(out.data_ptr()), \
|
| 1631 |
reinterpret_cast<c10::Half const *>(x.data_ptr()), \
|
| 1632 |
packed_weight_indices.data_ptr<uint8_t>(), \
|
| 1633 |
+
row_norms.data_ptr<c10::BFloat16>(), \
|
| 1634 |
centroids.data_ptr<float>(), \
|
| 1635 |
+
has_bias ? bias.data_ptr<c10::Half>() : nullptr, \
|
| 1636 |
has_bias, \
|
| 1637 |
x.size(0), \
|
| 1638 |
out_features, \
|
|
|
|
| 1674 |
out.data_ptr<scalar_t>(),
|
| 1675 |
x.data_ptr<scalar_t>(),
|
| 1676 |
packed_weight_indices.data_ptr<uint8_t>(),
|
| 1677 |
+
row_norms.data_ptr<c10::BFloat16>(),
|
| 1678 |
centroids.data_ptr<float>(),
|
| 1679 |
+
has_bias ? bias.data_ptr<scalar_t>() : nullptr,
|
| 1680 |
has_bias,
|
| 1681 |
x.size(0),
|
| 1682 |
out_features,
|
|
|
|
| 1686 |
});
|
| 1687 |
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 1688 |
}
|
| 1689 |
+
|
| 1690 |
+
void matmul_packed_w4a4_int8(
|
| 1691 |
+
torch::Tensor &out,
|
| 1692 |
+
torch::Tensor const &packed_activations,
|
| 1693 |
+
torch::Tensor const &packed_weight_indices,
|
| 1694 |
+
torch::Tensor const &token_norms,
|
| 1695 |
+
torch::Tensor const &row_norms,
|
| 1696 |
+
torch::Tensor const &activation_codes,
|
| 1697 |
+
torch::Tensor const &weight_codes,
|
| 1698 |
+
torch::Tensor const &bias,
|
| 1699 |
+
bool has_bias,
|
| 1700 |
+
double activation_scale,
|
| 1701 |
+
double weight_scale,
|
| 1702 |
+
int64_t out_features,
|
| 1703 |
+
int64_t in_features,
|
| 1704 |
+
int64_t tile_m,
|
| 1705 |
+
int64_t tile_n,
|
| 1706 |
+
bool async_packed,
|
| 1707 |
+
bool weight_k_major) {
|
| 1708 |
+
TORCH_CHECK(out.device().is_cuda(), "out must be a CUDA tensor");
|
| 1709 |
+
TORCH_CHECK(
|
| 1710 |
+
packed_activations.device().is_cuda(),
|
| 1711 |
+
"packed activations must be a CUDA tensor");
|
| 1712 |
+
TORCH_CHECK(
|
| 1713 |
+
packed_weight_indices.device().is_cuda(),
|
| 1714 |
+
"packed weights must be a CUDA tensor");
|
| 1715 |
+
TORCH_CHECK(token_norms.device().is_cuda(), "token norms must be a CUDA tensor");
|
| 1716 |
+
TORCH_CHECK(row_norms.device().is_cuda(), "row norms must be a CUDA tensor");
|
| 1717 |
+
TORCH_CHECK(
|
| 1718 |
+
activation_codes.device().is_cuda(),
|
| 1719 |
+
"activation surrogate codes must be a CUDA tensor");
|
| 1720 |
+
TORCH_CHECK(
|
| 1721 |
+
weight_codes.device().is_cuda(),
|
| 1722 |
+
"weight surrogate codes must be a CUDA tensor");
|
| 1723 |
+
TORCH_CHECK(out.is_contiguous(), "out must be contiguous");
|
| 1724 |
+
TORCH_CHECK(
|
| 1725 |
+
packed_activations.is_contiguous(), "packed activations must be contiguous");
|
| 1726 |
+
TORCH_CHECK(
|
| 1727 |
+
packed_weight_indices.is_contiguous(), "packed weights must be contiguous");
|
| 1728 |
+
TORCH_CHECK(token_norms.is_contiguous(), "token norms must be contiguous");
|
| 1729 |
+
TORCH_CHECK(row_norms.is_contiguous(), "row norms must be contiguous");
|
| 1730 |
+
TORCH_CHECK(
|
| 1731 |
+
activation_codes.is_contiguous(), "activation surrogate codes must be contiguous");
|
| 1732 |
+
TORCH_CHECK(
|
| 1733 |
+
weight_codes.is_contiguous(), "weight surrogate codes must be contiguous");
|
| 1734 |
+
TORCH_CHECK(
|
| 1735 |
+
packed_activations.scalar_type() == torch::kUInt8,
|
| 1736 |
+
"packed activations must be uint8");
|
| 1737 |
+
TORCH_CHECK(
|
| 1738 |
+
packed_weight_indices.scalar_type() == torch::kUInt8,
|
| 1739 |
+
"packed weights must be uint8");
|
| 1740 |
+
TORCH_CHECK(token_norms.scalar_type() == torch::kFloat, "token norms must be float32");
|
| 1741 |
+
TORCH_CHECK(row_norms.scalar_type() == torch::kBFloat16, "row norms must be bfloat16");
|
| 1742 |
+
TORCH_CHECK(
|
| 1743 |
+
activation_codes.scalar_type() == torch::kChar,
|
| 1744 |
+
"activation surrogate codes must be int8");
|
| 1745 |
+
TORCH_CHECK(
|
| 1746 |
+
weight_codes.scalar_type() == torch::kChar,
|
| 1747 |
+
"weight surrogate codes must be int8");
|
| 1748 |
+
TORCH_CHECK(
|
| 1749 |
+
out.scalar_type() == torch::kBFloat16 || out.scalar_type() == torch::kHalf,
|
| 1750 |
+
"packed W4A4 INT8 output must be bfloat16 or float16");
|
| 1751 |
+
TORCH_CHECK(packed_activations.dim() == 2, "packed activations must be rank 2");
|
| 1752 |
+
TORCH_CHECK(out.dim() == 2, "out must be rank 2");
|
| 1753 |
+
TORCH_CHECK(
|
| 1754 |
+
in_features > 0 && in_features % 64 == 0,
|
| 1755 |
+
"in_features must be positive and divisible by 64");
|
| 1756 |
+
TORCH_CHECK(out_features >= 0, "out_features must be non-negative");
|
| 1757 |
+
TORCH_CHECK(
|
| 1758 |
+
(tile_m == 128 && tile_n == 128) ||
|
| 1759 |
+
(tile_m == 256 && tile_n == 128) ||
|
| 1760 |
+
(tile_m == 128 && tile_n == 256),
|
| 1761 |
+
"packed W4A4 INT8 tile must be 128x128, 256x128, or 128x256");
|
| 1762 |
+
TORCH_CHECK(
|
| 1763 |
+
packed_activations.size(1) == in_features / 2,
|
| 1764 |
+
"packed activations have an unexpected input dimension");
|
| 1765 |
+
const int64_t rows = packed_activations.size(0);
|
| 1766 |
+
TORCH_CHECK(out.size(0) == rows, "out has an unexpected row count");
|
| 1767 |
+
TORCH_CHECK(out.size(1) == out_features, "out has an unexpected output dimension");
|
| 1768 |
+
TORCH_CHECK(token_norms.numel() == rows, "token norms must match rows");
|
| 1769 |
+
TORCH_CHECK(row_norms.numel() == out_features, "row norms must match out_features");
|
| 1770 |
+
TORCH_CHECK(activation_codes.numel() == 16, "activation codes must contain 16 values");
|
| 1771 |
+
TORCH_CHECK(weight_codes.numel() == 16, "weight codes must contain 16 values");
|
| 1772 |
+
TORCH_CHECK(
|
| 1773 |
+
packed_weight_indices.numel() == out_features * (in_features / 2),
|
| 1774 |
+
"K-major packed weights have an unexpected size");
|
| 1775 |
+
if (has_bias) {
|
| 1776 |
+
TORCH_CHECK(bias.device().is_cuda(), "bias must be a CUDA tensor");
|
| 1777 |
+
TORCH_CHECK(bias.is_contiguous(), "bias must be contiguous");
|
| 1778 |
+
TORCH_CHECK(bias.scalar_type() == out.scalar_type(), "bias dtype must match out");
|
| 1779 |
+
TORCH_CHECK(bias.numel() == out_features, "bias must match out_features");
|
| 1780 |
+
}
|
| 1781 |
+
if (rows == 0 || out_features == 0) {
|
| 1782 |
+
return;
|
| 1783 |
+
}
|
| 1784 |
+
|
| 1785 |
+
const at::cuda::OptionalCUDAGuard device_guard(device_of(packed_activations));
|
| 1786 |
+
const cudaDeviceProp *properties = at::cuda::getCurrentDeviceProperties();
|
| 1787 |
+
TORCH_CHECK(
|
| 1788 |
+
properties->major > 7 || (properties->major == 7 && properties->minor >= 5),
|
| 1789 |
+
"packed W4A4 INT8 Tensor Core matmul requires compute capability 7.5+");
|
| 1790 |
+
const int warp_count = static_cast<int>((tile_m / 16) * (tile_n / 128));
|
| 1791 |
+
const dim3 block(warp_count * 32);
|
| 1792 |
+
const dim3 grid(
|
| 1793 |
+
(out_features + tile_n - 1) / tile_n,
|
| 1794 |
+
(rows + tile_m - 1) / tile_m);
|
| 1795 |
+
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
| 1796 |
+
const int shared_bytes = static_cast<int>(
|
| 1797 |
+
tile_m * 80 + tile_n * 80 + warp_count * 16 * 16 * sizeof(int32_t) + 32 +
|
| 1798 |
+
(async_packed ? (tile_m + tile_n) * 32 : 0));
|
| 1799 |
+
TORCH_CHECK(
|
| 1800 |
+
shared_bytes <= properties->sharedMemPerBlockOptin,
|
| 1801 |
+
"packed W4A4 INT8 tile requires ",
|
| 1802 |
+
shared_bytes,
|
| 1803 |
+
" bytes of shared memory, but the device supports ",
|
| 1804 |
+
properties->sharedMemPerBlockOptin);
|
| 1805 |
+
|
| 1806 |
+
#define ORBITQUANT_LAUNCH_PACKED_W4A4_INT8( \
|
| 1807 |
+
STORAGE_TYPE, TILE_M, TILE_N, ASYNC_PACKED, K_MAJOR_WEIGHT) \
|
| 1808 |
+
if (shared_bytes > properties->sharedMemPerBlock) { \
|
| 1809 |
+
C10_CUDA_CHECK(cudaFuncSetAttribute( \
|
| 1810 |
+
orbitquant_packed_w4a4_int8_mma_kernel< \
|
| 1811 |
+
STORAGE_TYPE, TILE_M, TILE_N, ASYNC_PACKED, K_MAJOR_WEIGHT>, \
|
| 1812 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize, \
|
| 1813 |
+
shared_bytes)); \
|
| 1814 |
+
} \
|
| 1815 |
+
orbitquant_packed_w4a4_int8_mma_kernel< \
|
| 1816 |
+
STORAGE_TYPE, TILE_M, TILE_N, ASYNC_PACKED, K_MAJOR_WEIGHT> \
|
| 1817 |
+
<<<grid, block, shared_bytes, stream>>>( \
|
| 1818 |
+
reinterpret_cast<STORAGE_TYPE *>(out.data_ptr()), \
|
| 1819 |
+
packed_activations.data_ptr<uint8_t>(), \
|
| 1820 |
+
packed_weight_indices.data_ptr<uint8_t>(), \
|
| 1821 |
+
token_norms.data_ptr<float>(), \
|
| 1822 |
+
row_norms.data_ptr<c10::BFloat16>(), \
|
| 1823 |
+
activation_codes.data_ptr<int8_t>(), \
|
| 1824 |
+
weight_codes.data_ptr<int8_t>(), \
|
| 1825 |
+
has_bias ? bias.data_ptr<STORAGE_TYPE>() : nullptr, \
|
| 1826 |
+
has_bias, \
|
| 1827 |
+
static_cast<float>(activation_scale), \
|
| 1828 |
+
static_cast<float>(weight_scale), \
|
| 1829 |
+
rows, \
|
| 1830 |
+
out_features, \
|
| 1831 |
+
in_features)
|
| 1832 |
+
#define ORBITQUANT_DISPATCH_PACKED_W4A4_TILE( \
|
| 1833 |
+
STORAGE_TYPE, ASYNC_PACKED, K_MAJOR_WEIGHT) \
|
| 1834 |
+
if (tile_m == 256) { \
|
| 1835 |
+
ORBITQUANT_LAUNCH_PACKED_W4A4_INT8( \
|
| 1836 |
+
STORAGE_TYPE, 256, 128, ASYNC_PACKED, K_MAJOR_WEIGHT); \
|
| 1837 |
+
} else if (tile_n == 256) { \
|
| 1838 |
+
ORBITQUANT_LAUNCH_PACKED_W4A4_INT8( \
|
| 1839 |
+
STORAGE_TYPE, 128, 256, ASYNC_PACKED, K_MAJOR_WEIGHT); \
|
| 1840 |
+
} else { \
|
| 1841 |
+
ORBITQUANT_LAUNCH_PACKED_W4A4_INT8( \
|
| 1842 |
+
STORAGE_TYPE, 128, 128, ASYNC_PACKED, K_MAJOR_WEIGHT); \
|
| 1843 |
+
}
|
| 1844 |
+
#define ORBITQUANT_DISPATCH_PACKED_W4A4_LAYOUT(STORAGE_TYPE, ASYNC_PACKED) \
|
| 1845 |
+
if (weight_k_major) { \
|
| 1846 |
+
ORBITQUANT_DISPATCH_PACKED_W4A4_TILE(STORAGE_TYPE, ASYNC_PACKED, true); \
|
| 1847 |
+
} else { \
|
| 1848 |
+
ORBITQUANT_DISPATCH_PACKED_W4A4_TILE(STORAGE_TYPE, ASYNC_PACKED, false); \
|
| 1849 |
+
}
|
| 1850 |
+
if (out.scalar_type() == torch::kBFloat16) {
|
| 1851 |
+
if (async_packed) {
|
| 1852 |
+
ORBITQUANT_DISPATCH_PACKED_W4A4_LAYOUT(c10::BFloat16, true);
|
| 1853 |
+
} else {
|
| 1854 |
+
ORBITQUANT_DISPATCH_PACKED_W4A4_LAYOUT(c10::BFloat16, false);
|
| 1855 |
+
}
|
| 1856 |
+
} else {
|
| 1857 |
+
if (async_packed) {
|
| 1858 |
+
ORBITQUANT_DISPATCH_PACKED_W4A4_LAYOUT(c10::Half, true);
|
| 1859 |
+
} else {
|
| 1860 |
+
ORBITQUANT_DISPATCH_PACKED_W4A4_LAYOUT(c10::Half, false);
|
| 1861 |
+
}
|
| 1862 |
+
}
|
| 1863 |
+
#undef ORBITQUANT_DISPATCH_PACKED_W4A4_LAYOUT
|
| 1864 |
+
#undef ORBITQUANT_DISPATCH_PACKED_W4A4_TILE
|
| 1865 |
+
#undef ORBITQUANT_LAUNCH_PACKED_W4A4_INT8
|
| 1866 |
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 1867 |
+
}
|
| 1868 |
+
|
| 1869 |
+
void quantize_activations_packed_w4(
|
| 1870 |
+
torch::Tensor &packed_out,
|
| 1871 |
+
torch::Tensor &norms_out,
|
| 1872 |
+
torch::Tensor const &x,
|
| 1873 |
+
torch::Tensor const &permutation,
|
| 1874 |
+
torch::Tensor const &signs,
|
| 1875 |
+
torch::Tensor const &boundaries,
|
| 1876 |
+
double eps,
|
| 1877 |
+
double inv_sqrt_block,
|
| 1878 |
+
int64_t threads) {
|
| 1879 |
+
TORCH_CHECK(packed_out.device().is_cuda(), "packed_out must be a CUDA tensor");
|
| 1880 |
+
TORCH_CHECK(norms_out.device().is_cuda(), "norms_out must be a CUDA tensor");
|
| 1881 |
+
TORCH_CHECK(x.device().is_cuda(), "x must be a CUDA tensor");
|
| 1882 |
+
TORCH_CHECK(permutation.device().is_cuda(), "permutation must be a CUDA tensor");
|
| 1883 |
+
TORCH_CHECK(signs.device().is_cuda(), "signs must be a CUDA tensor");
|
| 1884 |
+
TORCH_CHECK(boundaries.device().is_cuda(), "boundaries must be a CUDA tensor");
|
| 1885 |
+
TORCH_CHECK(packed_out.is_contiguous(), "packed_out must be contiguous");
|
| 1886 |
+
TORCH_CHECK(norms_out.is_contiguous(), "norms_out must be contiguous");
|
| 1887 |
+
TORCH_CHECK(x.is_contiguous(), "x must be contiguous");
|
| 1888 |
+
TORCH_CHECK(permutation.is_contiguous(), "permutation must be contiguous");
|
| 1889 |
+
TORCH_CHECK(signs.is_contiguous(), "signs must be contiguous");
|
| 1890 |
+
TORCH_CHECK(boundaries.is_contiguous(), "boundaries must be contiguous");
|
| 1891 |
+
TORCH_CHECK(
|
| 1892 |
+
packed_out.scalar_type() == torch::kUInt8,
|
| 1893 |
+
"packed_out must be uint8");
|
| 1894 |
+
TORCH_CHECK(norms_out.scalar_type() == torch::kFloat, "norms_out must be float32");
|
| 1895 |
+
TORCH_CHECK(
|
| 1896 |
+
x.scalar_type() == torch::kBFloat16 || x.scalar_type() == torch::kHalf,
|
| 1897 |
+
"x must be bfloat16 or float16");
|
| 1898 |
+
TORCH_CHECK(
|
| 1899 |
+
permutation.scalar_type() == torch::kLong ||
|
| 1900 |
+
permutation.scalar_type() == torch::kInt,
|
| 1901 |
+
"permutation must be int32 or int64");
|
| 1902 |
+
TORCH_CHECK(signs.scalar_type() == torch::kChar, "signs must be int8");
|
| 1903 |
+
TORCH_CHECK(boundaries.scalar_type() == torch::kFloat, "boundaries must be float32");
|
| 1904 |
+
TORCH_CHECK(x.dim() == 2, "x must be rank 2");
|
| 1905 |
+
TORCH_CHECK(packed_out.dim() == 2, "packed_out must be rank 2");
|
| 1906 |
+
TORCH_CHECK(norms_out.dim() == 1, "norms_out must be rank 1");
|
| 1907 |
+
const int64_t rows = x.size(0);
|
| 1908 |
+
const int64_t dim = x.size(1);
|
| 1909 |
+
TORCH_CHECK(
|
| 1910 |
+
dim == 512 || dim == 1024 || dim == 2048 || dim == 4096 ||
|
| 1911 |
+
dim == 8192 || dim == 16384,
|
| 1912 |
+
"native packed W4 activation quantization supports dimensions "
|
| 1913 |
+
"512, 1024, 2048, 4096, 8192, and 16384");
|
| 1914 |
+
TORCH_CHECK(
|
| 1915 |
+
threads == 128 || threads == 256 || threads == 512,
|
| 1916 |
+
"native packed W4 activation quantization threads must be 128, 256, or 512");
|
| 1917 |
+
TORCH_CHECK(
|
| 1918 |
+
packed_out.size(0) == rows && packed_out.size(1) == dim / 2,
|
| 1919 |
+
"packed_out has an unexpected shape");
|
| 1920 |
+
TORCH_CHECK(norms_out.numel() == rows, "norms_out must match rows");
|
| 1921 |
+
TORCH_CHECK(permutation.numel() == dim, "permutation must match the input dimension");
|
| 1922 |
+
TORCH_CHECK(signs.numel() == dim, "signs must match the input dimension");
|
| 1923 |
+
TORCH_CHECK(boundaries.numel() == 15, "boundaries must contain 15 values");
|
| 1924 |
+
if (rows == 0) {
|
| 1925 |
+
return;
|
| 1926 |
+
}
|
| 1927 |
+
|
| 1928 |
+
const at::cuda::OptionalCUDAGuard device_guard(device_of(x));
|
| 1929 |
+
const cudaDeviceProp *properties = at::cuda::getCurrentDeviceProperties();
|
| 1930 |
+
const int shared_bytes = static_cast<int>((dim + threads + 15) * sizeof(float));
|
| 1931 |
+
TORCH_CHECK(
|
| 1932 |
+
shared_bytes <= properties->sharedMemPerBlockOptin,
|
| 1933 |
+
"native packed W4 activation quantization requires ",
|
| 1934 |
+
shared_bytes,
|
| 1935 |
+
" bytes of shared memory, but the device supports ",
|
| 1936 |
+
properties->sharedMemPerBlockOptin);
|
| 1937 |
+
const dim3 block(static_cast<unsigned int>(threads));
|
| 1938 |
+
const dim3 grid(static_cast<unsigned int>(rows));
|
| 1939 |
+
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
| 1940 |
+
|
| 1941 |
+
#define ORBITQUANT_LAUNCH_RPBH_PACK_W4(STORAGE_TYPE, INDEX_TYPE, DIM_VALUE) \
|
| 1942 |
+
if (shared_bytes > properties->sharedMemPerBlock) { \
|
| 1943 |
+
C10_CUDA_CHECK(cudaFuncSetAttribute( \
|
| 1944 |
+
orbitquant_rpbh_quantize_pack_w4_kernel<STORAGE_TYPE, INDEX_TYPE, \
|
| 1945 |
+
DIM_VALUE>, \
|
| 1946 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize, \
|
| 1947 |
+
shared_bytes)); \
|
| 1948 |
+
} \
|
| 1949 |
+
orbitquant_rpbh_quantize_pack_w4_kernel<STORAGE_TYPE, INDEX_TYPE, DIM_VALUE> \
|
| 1950 |
+
<<<grid, block, shared_bytes, stream>>>( \
|
| 1951 |
+
packed_out.data_ptr<uint8_t>(), \
|
| 1952 |
+
norms_out.data_ptr<float>(), \
|
| 1953 |
+
reinterpret_cast<STORAGE_TYPE const *>(x.data_ptr()), \
|
| 1954 |
+
permutation.data_ptr<INDEX_TYPE>(), \
|
| 1955 |
+
signs.data_ptr<int8_t>(), \
|
| 1956 |
+
boundaries.data_ptr<float>(), \
|
| 1957 |
+
static_cast<float>(eps), \
|
| 1958 |
+
static_cast<float>(inv_sqrt_block), \
|
| 1959 |
+
rows)
|
| 1960 |
+
#define ORBITQUANT_DISPATCH_RPBH_PACK_W4(STORAGE_TYPE, INDEX_TYPE) \
|
| 1961 |
+
switch (dim) { \
|
| 1962 |
+
case 512: \
|
| 1963 |
+
ORBITQUANT_LAUNCH_RPBH_PACK_W4(STORAGE_TYPE, INDEX_TYPE, 512); \
|
| 1964 |
+
break; \
|
| 1965 |
+
case 1024: \
|
| 1966 |
+
ORBITQUANT_LAUNCH_RPBH_PACK_W4(STORAGE_TYPE, INDEX_TYPE, 1024); \
|
| 1967 |
+
break; \
|
| 1968 |
+
case 2048: \
|
| 1969 |
+
ORBITQUANT_LAUNCH_RPBH_PACK_W4(STORAGE_TYPE, INDEX_TYPE, 2048); \
|
| 1970 |
+
break; \
|
| 1971 |
+
case 4096: \
|
| 1972 |
+
ORBITQUANT_LAUNCH_RPBH_PACK_W4(STORAGE_TYPE, INDEX_TYPE, 4096); \
|
| 1973 |
+
break; \
|
| 1974 |
+
case 8192: \
|
| 1975 |
+
ORBITQUANT_LAUNCH_RPBH_PACK_W4(STORAGE_TYPE, INDEX_TYPE, 8192); \
|
| 1976 |
+
break; \
|
| 1977 |
+
case 16384: \
|
| 1978 |
+
ORBITQUANT_LAUNCH_RPBH_PACK_W4(STORAGE_TYPE, INDEX_TYPE, 16384); \
|
| 1979 |
+
break; \
|
| 1980 |
+
}
|
| 1981 |
+
const bool int32_permutation = permutation.scalar_type() == torch::kInt;
|
| 1982 |
+
if (x.scalar_type() == torch::kBFloat16) {
|
| 1983 |
+
if (int32_permutation) {
|
| 1984 |
+
ORBITQUANT_DISPATCH_RPBH_PACK_W4(c10::BFloat16, int32_t);
|
| 1985 |
+
} else {
|
| 1986 |
+
ORBITQUANT_DISPATCH_RPBH_PACK_W4(c10::BFloat16, int64_t);
|
| 1987 |
+
}
|
| 1988 |
+
} else {
|
| 1989 |
+
if (int32_permutation) {
|
| 1990 |
+
ORBITQUANT_DISPATCH_RPBH_PACK_W4(c10::Half, int32_t);
|
| 1991 |
+
} else {
|
| 1992 |
+
ORBITQUANT_DISPATCH_RPBH_PACK_W4(c10::Half, int64_t);
|
| 1993 |
+
}
|
| 1994 |
+
}
|
| 1995 |
+
#undef ORBITQUANT_DISPATCH_RPBH_PACK_W4
|
| 1996 |
+
#undef ORBITQUANT_LAUNCH_RPBH_PACK_W4
|
| 1997 |
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 1998 |
+
}
|
| 1999 |
+
|
| 2000 |
+
void quantize_activations_int8(
|
| 2001 |
+
torch::Tensor &int8_out,
|
| 2002 |
+
torch::Tensor &norms_out,
|
| 2003 |
+
torch::Tensor const &x,
|
| 2004 |
+
torch::Tensor const &permutation,
|
| 2005 |
+
torch::Tensor const &signs,
|
| 2006 |
+
torch::Tensor const &boundaries,
|
| 2007 |
+
torch::Tensor const &codes,
|
| 2008 |
+
double eps,
|
| 2009 |
+
double inv_sqrt_block,
|
| 2010 |
+
int64_t threads) {
|
| 2011 |
+
TORCH_CHECK(int8_out.device().is_cuda(), "int8_out must be a CUDA tensor");
|
| 2012 |
+
TORCH_CHECK(norms_out.device().is_cuda(), "norms_out must be a CUDA tensor");
|
| 2013 |
+
TORCH_CHECK(x.device().is_cuda(), "x must be a CUDA tensor");
|
| 2014 |
+
TORCH_CHECK(permutation.device().is_cuda(), "permutation must be a CUDA tensor");
|
| 2015 |
+
TORCH_CHECK(signs.device().is_cuda(), "signs must be a CUDA tensor");
|
| 2016 |
+
TORCH_CHECK(boundaries.device().is_cuda(), "boundaries must be a CUDA tensor");
|
| 2017 |
+
TORCH_CHECK(codes.device().is_cuda(), "codes must be a CUDA tensor");
|
| 2018 |
+
TORCH_CHECK(int8_out.is_contiguous(), "int8_out must be contiguous");
|
| 2019 |
+
TORCH_CHECK(norms_out.is_contiguous(), "norms_out must be contiguous");
|
| 2020 |
+
TORCH_CHECK(x.is_contiguous(), "x must be contiguous");
|
| 2021 |
+
TORCH_CHECK(permutation.is_contiguous(), "permutation must be contiguous");
|
| 2022 |
+
TORCH_CHECK(signs.is_contiguous(), "signs must be contiguous");
|
| 2023 |
+
TORCH_CHECK(boundaries.is_contiguous(), "boundaries must be contiguous");
|
| 2024 |
+
TORCH_CHECK(codes.is_contiguous(), "codes must be contiguous");
|
| 2025 |
+
TORCH_CHECK(int8_out.scalar_type() == torch::kChar, "int8_out must be int8");
|
| 2026 |
+
TORCH_CHECK(norms_out.scalar_type() == torch::kFloat, "norms_out must be float32");
|
| 2027 |
+
TORCH_CHECK(
|
| 2028 |
+
x.scalar_type() == torch::kBFloat16 || x.scalar_type() == torch::kHalf,
|
| 2029 |
+
"x must be bfloat16 or float16");
|
| 2030 |
+
TORCH_CHECK(
|
| 2031 |
+
permutation.scalar_type() == torch::kLong ||
|
| 2032 |
+
permutation.scalar_type() == torch::kInt,
|
| 2033 |
+
"permutation must be int32 or int64");
|
| 2034 |
+
TORCH_CHECK(signs.scalar_type() == torch::kChar, "signs must be int8");
|
| 2035 |
+
TORCH_CHECK(boundaries.scalar_type() == torch::kFloat, "boundaries must be float32");
|
| 2036 |
+
TORCH_CHECK(codes.scalar_type() == torch::kChar, "codes must be int8");
|
| 2037 |
+
TORCH_CHECK(x.dim() == 2, "x must be rank 2");
|
| 2038 |
+
TORCH_CHECK(int8_out.dim() == 2, "int8_out must be rank 2");
|
| 2039 |
+
TORCH_CHECK(norms_out.dim() == 1, "norms_out must be rank 1");
|
| 2040 |
+
const int64_t rows = x.size(0);
|
| 2041 |
+
const int64_t dim = x.size(1);
|
| 2042 |
+
TORCH_CHECK(
|
| 2043 |
+
dim == 512 || dim == 1024 || dim == 2048 || dim == 4096 ||
|
| 2044 |
+
dim == 8192 || dim == 12288 || dim == 16384,
|
| 2045 |
+
"native INT8 activation quantization supports dimensions "
|
| 2046 |
+
"512, 1024, 2048, 4096, 8192, 12288, and 16384");
|
| 2047 |
+
TORCH_CHECK(
|
| 2048 |
+
threads == 128 || threads == 256 || threads == 512,
|
| 2049 |
+
"native INT8 activation quantization threads must be 128, 256, or 512");
|
| 2050 |
+
TORCH_CHECK(
|
| 2051 |
+
int8_out.size(0) == rows && int8_out.size(1) == dim,
|
| 2052 |
+
"int8_out has an unexpected shape");
|
| 2053 |
+
TORCH_CHECK(norms_out.numel() == rows, "norms_out must match rows");
|
| 2054 |
+
TORCH_CHECK(permutation.numel() == dim, "permutation must match the input dimension");
|
| 2055 |
+
TORCH_CHECK(signs.numel() == dim, "signs must match the input dimension");
|
| 2056 |
+
TORCH_CHECK(boundaries.numel() == 15, "boundaries must contain 15 values");
|
| 2057 |
+
TORCH_CHECK(codes.numel() == 16, "codes must contain 16 values");
|
| 2058 |
+
if (rows == 0) {
|
| 2059 |
+
return;
|
| 2060 |
+
}
|
| 2061 |
+
|
| 2062 |
+
const at::cuda::OptionalCUDAGuard device_guard(device_of(x));
|
| 2063 |
+
const cudaDeviceProp *properties = at::cuda::getCurrentDeviceProperties();
|
| 2064 |
+
const int shared_bytes =
|
| 2065 |
+
static_cast<int>((dim + threads + 15) * sizeof(float) + 16);
|
| 2066 |
+
TORCH_CHECK(
|
| 2067 |
+
shared_bytes <= properties->sharedMemPerBlockOptin,
|
| 2068 |
+
"native INT8 activation quantization requires ",
|
| 2069 |
+
shared_bytes,
|
| 2070 |
+
" bytes of shared memory, but the device supports ",
|
| 2071 |
+
properties->sharedMemPerBlockOptin);
|
| 2072 |
+
const dim3 block(static_cast<unsigned int>(threads));
|
| 2073 |
+
const dim3 grid(static_cast<unsigned int>(rows));
|
| 2074 |
+
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
| 2075 |
+
|
| 2076 |
+
#define ORBITQUANT_LAUNCH_RPBH_INT8( \
|
| 2077 |
+
STORAGE_TYPE, INDEX_TYPE, DIM_VALUE, ORBIT_BLOCK_VALUE) \
|
| 2078 |
+
if (shared_bytes > properties->sharedMemPerBlock) { \
|
| 2079 |
+
C10_CUDA_CHECK(cudaFuncSetAttribute( \
|
| 2080 |
+
orbitquant_rpbh_quantize_int8_kernel< \
|
| 2081 |
+
STORAGE_TYPE, INDEX_TYPE, DIM_VALUE, ORBIT_BLOCK_VALUE>, \
|
| 2082 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize, \
|
| 2083 |
+
shared_bytes)); \
|
| 2084 |
+
} \
|
| 2085 |
+
orbitquant_rpbh_quantize_int8_kernel< \
|
| 2086 |
+
STORAGE_TYPE, INDEX_TYPE, DIM_VALUE, ORBIT_BLOCK_VALUE> \
|
| 2087 |
+
<<<grid, block, shared_bytes, stream>>>( \
|
| 2088 |
+
int8_out.data_ptr<int8_t>(), \
|
| 2089 |
+
norms_out.data_ptr<float>(), \
|
| 2090 |
+
reinterpret_cast<STORAGE_TYPE const *>(x.data_ptr()), \
|
| 2091 |
+
permutation.data_ptr<INDEX_TYPE>(), \
|
| 2092 |
+
signs.data_ptr<int8_t>(), \
|
| 2093 |
+
boundaries.data_ptr<float>(), \
|
| 2094 |
+
codes.data_ptr<int8_t>(), \
|
| 2095 |
+
static_cast<float>(eps), \
|
| 2096 |
+
static_cast<float>(inv_sqrt_block), \
|
| 2097 |
+
rows)
|
| 2098 |
+
#define ORBITQUANT_DISPATCH_RPBH_INT8(STORAGE_TYPE, INDEX_TYPE) \
|
| 2099 |
+
switch (dim) { \
|
| 2100 |
+
case 512: \
|
| 2101 |
+
ORBITQUANT_LAUNCH_RPBH_INT8(STORAGE_TYPE, INDEX_TYPE, 512, 512); \
|
| 2102 |
+
break; \
|
| 2103 |
+
case 1024: \
|
| 2104 |
+
ORBITQUANT_LAUNCH_RPBH_INT8(STORAGE_TYPE, INDEX_TYPE, 1024, 1024); \
|
| 2105 |
+
break; \
|
| 2106 |
+
case 2048: \
|
| 2107 |
+
ORBITQUANT_LAUNCH_RPBH_INT8(STORAGE_TYPE, INDEX_TYPE, 2048, 2048); \
|
| 2108 |
+
break; \
|
| 2109 |
+
case 4096: \
|
| 2110 |
+
ORBITQUANT_LAUNCH_RPBH_INT8(STORAGE_TYPE, INDEX_TYPE, 4096, 4096); \
|
| 2111 |
+
break; \
|
| 2112 |
+
case 8192: \
|
| 2113 |
+
ORBITQUANT_LAUNCH_RPBH_INT8(STORAGE_TYPE, INDEX_TYPE, 8192, 8192); \
|
| 2114 |
+
break; \
|
| 2115 |
+
case 12288: \
|
| 2116 |
+
ORBITQUANT_LAUNCH_RPBH_INT8(STORAGE_TYPE, INDEX_TYPE, 12288, 4096); \
|
| 2117 |
+
break; \
|
| 2118 |
+
case 16384: \
|
| 2119 |
+
ORBITQUANT_LAUNCH_RPBH_INT8(STORAGE_TYPE, INDEX_TYPE, 16384, 16384); \
|
| 2120 |
+
break; \
|
| 2121 |
+
}
|
| 2122 |
+
const bool int32_permutation = permutation.scalar_type() == torch::kInt;
|
| 2123 |
+
if (x.scalar_type() == torch::kBFloat16) {
|
| 2124 |
+
if (int32_permutation) {
|
| 2125 |
+
ORBITQUANT_DISPATCH_RPBH_INT8(c10::BFloat16, int32_t);
|
| 2126 |
+
} else {
|
| 2127 |
+
ORBITQUANT_DISPATCH_RPBH_INT8(c10::BFloat16, int64_t);
|
| 2128 |
+
}
|
| 2129 |
+
} else {
|
| 2130 |
+
if (int32_permutation) {
|
| 2131 |
+
ORBITQUANT_DISPATCH_RPBH_INT8(c10::Half, int32_t);
|
| 2132 |
+
} else {
|
| 2133 |
+
ORBITQUANT_DISPATCH_RPBH_INT8(c10::Half, int64_t);
|
| 2134 |
+
}
|
| 2135 |
+
}
|
| 2136 |
+
#undef ORBITQUANT_DISPATCH_RPBH_INT8
|
| 2137 |
+
#undef ORBITQUANT_LAUNCH_RPBH_INT8
|
| 2138 |
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 2139 |
+
}
|
orbitquant_packed_matmul_metal/packed_matmul.metal
CHANGED
|
@@ -11,14 +11,120 @@ struct PackedMatmulParams {
|
|
| 11 |
int has_bias;
|
| 12 |
};
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
}
|
| 23 |
|
| 24 |
template <typename scalar_t>
|
|
@@ -102,83 +208,214 @@ inline void packed_matmul_tiled_value(
|
|
| 102 |
}
|
| 103 |
}
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
device const uchar *packed_weight_indices,
|
| 109 |
device const float *row_norms,
|
| 110 |
device const float *centroids,
|
| 111 |
device const float *bias,
|
| 112 |
constant PackedMatmulParams ¶ms,
|
| 113 |
-
threadgroup
|
| 114 |
-
uint2
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
const long
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
const
|
| 124 |
-
const
|
| 125 |
-
const
|
| 126 |
-
const
|
| 127 |
-
|
| 128 |
-
const
|
| 129 |
-
float acc = output_valid && params.has_bias != 0 ? bias[col] : 0.0f;
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
const long global_k = k_start + tile_k;
|
| 138 |
-
float value = 0.0f;
|
| 139 |
-
if (global_row < params.rows && global_k < params.in_features) {
|
| 140 |
-
value = bf16_to_float(x[global_row * params.in_features + global_k]);
|
| 141 |
-
}
|
| 142 |
-
x_tile[offset] = value;
|
| 143 |
}
|
|
|
|
| 144 |
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
if (bit_offset + params.bits > 8) {
|
| 159 |
-
raw |= uint(packed_weight_indices[byte_index + 1]) << 8;
|
| 160 |
-
}
|
| 161 |
-
const uint index = (raw >> uint(bit_offset)) & mask;
|
| 162 |
-
value = row_norms[global_col] * centroids[index];
|
| 163 |
}
|
| 164 |
-
w_tile[offset] = value;
|
| 165 |
}
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
threadgroup_barrier(mem_flags::mem_threadgroup);
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
}
|
| 175 |
}
|
| 176 |
-
|
| 177 |
-
threadgroup_barrier(mem_flags::mem_threadgroup);
|
| 178 |
}
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
}
|
| 183 |
}
|
| 184 |
|
|
@@ -209,6 +446,74 @@ kernel void packed_matmul_forward_float(
|
|
| 209 |
}
|
| 210 |
|
| 211 |
kernel void packed_matmul_forward_half(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
device half *out [[buffer(0)]],
|
| 213 |
device const half *x [[buffer(1)]],
|
| 214 |
device const uchar *packed_weight_indices [[buffer(2)]],
|
|
@@ -234,9 +539,9 @@ kernel void packed_matmul_forward_half(
|
|
| 234 |
threads_per_group);
|
| 235 |
}
|
| 236 |
|
| 237 |
-
kernel void
|
| 238 |
-
device
|
| 239 |
-
device const
|
| 240 |
device const uchar *packed_weight_indices [[buffer(2)]],
|
| 241 |
device const float *row_norms [[buffer(3)]],
|
| 242 |
device const float *centroids [[buffer(4)]],
|
|
@@ -246,7 +551,7 @@ kernel void packed_matmul_forward_bfloat16(
|
|
| 246 |
uint2 tid [[thread_position_in_grid]],
|
| 247 |
uint2 local_tid [[thread_position_in_threadgroup]],
|
| 248 |
uint2 threads_per_group [[threads_per_threadgroup]]) {
|
| 249 |
-
|
| 250 |
out,
|
| 251 |
x,
|
| 252 |
packed_weight_indices,
|
|
@@ -259,3 +564,51 @@ kernel void packed_matmul_forward_bfloat16(
|
|
| 259 |
local_tid,
|
| 260 |
threads_per_group);
|
| 261 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
int has_bias;
|
| 12 |
};
|
| 13 |
|
| 14 |
+
struct alignas(16) PackedMMAReadVector {
|
| 15 |
+
uchar values[16];
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
struct alignas(1) PackedMMARead3 {
|
| 19 |
+
uchar values[3];
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
constant uint packed_gemv_simdgroups = 8;
|
| 23 |
+
constant uint packed_gemv_max_rows = 8;
|
| 24 |
+
|
| 25 |
+
// Skinny-batch GEMV: one simdgroup owns one output column, the 32 lanes strip
|
| 26 |
+
// the K dimension in 8-value segments (Bits bytes each), and the decoded
|
| 27 |
+
// weights are reused across every row of the batch. The packed column is read
|
| 28 |
+
// with one vector load per segment, which is what makes this path faster than
|
| 29 |
+
// a dequantized F.linear for small row counts: the weight stream is 16/Bits
|
| 30 |
+
// times smaller.
|
| 31 |
+
template <typename scalar_t, uint Bits>
|
| 32 |
+
inline void packed_matmul_gemv_value(
|
| 33 |
+
device scalar_t *out,
|
| 34 |
+
device const scalar_t *x,
|
| 35 |
+
device const uchar *packed_weight_indices,
|
| 36 |
+
device const float *row_norms,
|
| 37 |
+
device const float *centroids,
|
| 38 |
+
device const float *bias,
|
| 39 |
+
constant PackedMatmulParams ¶ms,
|
| 40 |
+
threadgroup float *centroid_lut,
|
| 41 |
+
uint2 group_id,
|
| 42 |
+
uint thread_index,
|
| 43 |
+
ushort simdgroup_id,
|
| 44 |
+
ushort lane) {
|
| 45 |
+
constexpr uint mask = (1u << Bits) - 1u;
|
| 46 |
+
if (thread_index < (1u << Bits)) {
|
| 47 |
+
centroid_lut[thread_index] = centroids[thread_index];
|
| 48 |
+
}
|
| 49 |
+
threadgroup_barrier(mem_flags::mem_threadgroup);
|
| 50 |
+
|
| 51 |
+
const long col = long(group_id.x) * packed_gemv_simdgroups + simdgroup_id;
|
| 52 |
+
if (col >= params.out_features) {
|
| 53 |
+
return;
|
| 54 |
+
}
|
| 55 |
+
const long rows = min(params.rows, long(packed_gemv_max_rows));
|
| 56 |
+
device const uchar *column_bytes =
|
| 57 |
+
packed_weight_indices + (col * params.in_features * Bits) / 8;
|
| 58 |
+
|
| 59 |
+
float accumulators[packed_gemv_max_rows] = {};
|
| 60 |
+
|
| 61 |
+
// Each lane consumes an 8-value segment (Bits bytes) per step; the 32 lanes
|
| 62 |
+
// cover 256 values per iteration.
|
| 63 |
+
const long segments = params.in_features / 8;
|
| 64 |
+
for (long segment = long(lane); segment < segments; segment += 32) {
|
| 65 |
+
const long byte_index = segment * Bits;
|
| 66 |
+
uint low = 0;
|
| 67 |
+
uint high = 0;
|
| 68 |
+
if (Bits == 2) {
|
| 69 |
+
low = uint(*reinterpret_cast<device const ushort *>(
|
| 70 |
+
column_bytes + byte_index));
|
| 71 |
+
} else if (Bits == 3) {
|
| 72 |
+
const PackedMMARead3 bytes =
|
| 73 |
+
*reinterpret_cast<device const PackedMMARead3 *>(
|
| 74 |
+
column_bytes + byte_index);
|
| 75 |
+
low = uint(bytes.values[0]) | (uint(bytes.values[1]) << 8) |
|
| 76 |
+
(uint(bytes.values[2]) << 16);
|
| 77 |
+
} else if (Bits == 4) {
|
| 78 |
+
low = *reinterpret_cast<device const uint *>(column_bytes + byte_index);
|
| 79 |
+
} else {
|
| 80 |
+
// Six-byte segments are only 2-byte aligned; read three ushorts.
|
| 81 |
+
const ushort word0 = *reinterpret_cast<device const ushort *>(
|
| 82 |
+
column_bytes + byte_index);
|
| 83 |
+
const ushort word1 = *reinterpret_cast<device const ushort *>(
|
| 84 |
+
column_bytes + byte_index + 2);
|
| 85 |
+
const ushort word2 = *reinterpret_cast<device const ushort *>(
|
| 86 |
+
column_bytes + byte_index + 4);
|
| 87 |
+
low = uint(word0) | (uint(word1) << 16);
|
| 88 |
+
high = uint(word2);
|
| 89 |
+
}
|
| 90 |
|
| 91 |
+
float weights[8];
|
| 92 |
+
#pragma clang loop unroll(full)
|
| 93 |
+
for (uint idx = 0; idx < 8; ++idx) {
|
| 94 |
+
uint raw;
|
| 95 |
+
if (Bits == 6) {
|
| 96 |
+
const uint bit_start = idx * 6;
|
| 97 |
+
raw = bit_start < 32
|
| 98 |
+
? ((low >> bit_start) |
|
| 99 |
+
(bit_start > 26 ? (high << (32 - bit_start)) : 0u))
|
| 100 |
+
: (high >> (bit_start - 32));
|
| 101 |
+
} else {
|
| 102 |
+
raw = low >> (idx * Bits);
|
| 103 |
+
}
|
| 104 |
+
weights[idx] = centroid_lut[raw & mask];
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
const long k = segment * 8;
|
| 108 |
+
for (long row = 0; row < rows; ++row) {
|
| 109 |
+
device const scalar_t *x_row = x + row * params.in_features + k;
|
| 110 |
+
float partial = 0.0f;
|
| 111 |
+
#pragma clang loop unroll(full)
|
| 112 |
+
for (uint idx = 0; idx < 8; ++idx) {
|
| 113 |
+
partial += weights[idx] * float(x_row[idx]);
|
| 114 |
+
}
|
| 115 |
+
accumulators[row] += partial;
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
const float norm = row_norms[col];
|
| 120 |
+
const float bias_value = params.has_bias != 0 ? bias[col] : 0.0f;
|
| 121 |
+
for (long row = 0; row < rows; ++row) {
|
| 122 |
+
const float value = simd_sum(accumulators[row]);
|
| 123 |
+
if (lane == 0) {
|
| 124 |
+
out[row * params.out_features + col] =
|
| 125 |
+
scalar_t(value * norm + bias_value);
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
}
|
| 129 |
|
| 130 |
template <typename scalar_t>
|
|
|
|
| 208 |
}
|
| 209 |
}
|
| 210 |
|
| 211 |
+
constant uint packed_mma_tile = 32;
|
| 212 |
+
constant uint packed_mma_padded_k = 40;
|
| 213 |
+
|
| 214 |
+
template <typename scalar_t>
|
| 215 |
+
inline void packed_mma_fragment(
|
| 216 |
+
thread float2 &output,
|
| 217 |
+
thread vec<scalar_t, 2> &lhs,
|
| 218 |
+
thread vec<scalar_t, 2> &rhs,
|
| 219 |
+
thread float2 &accumulator) {
|
| 220 |
+
simdgroup_matrix<float, 8, 8> output_matrix;
|
| 221 |
+
simdgroup_matrix<scalar_t, 8, 8> lhs_matrix;
|
| 222 |
+
simdgroup_matrix<scalar_t, 8, 8> rhs_matrix;
|
| 223 |
+
simdgroup_matrix<float, 8, 8> accumulator_matrix;
|
| 224 |
+
reinterpret_cast<thread vec<scalar_t, 2> &>(lhs_matrix.thread_elements()) = lhs;
|
| 225 |
+
reinterpret_cast<thread vec<scalar_t, 2> &>(rhs_matrix.thread_elements()) = rhs;
|
| 226 |
+
reinterpret_cast<thread float2 &>(accumulator_matrix.thread_elements()) = accumulator;
|
| 227 |
+
simdgroup_multiply_accumulate(
|
| 228 |
+
output_matrix, lhs_matrix, rhs_matrix, accumulator_matrix);
|
| 229 |
+
output = reinterpret_cast<thread float2 &>(output_matrix.thread_elements());
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
template <typename scalar_t, uint Bits>
|
| 233 |
+
inline void decode_packed_mma_weight_segment(
|
| 234 |
+
threadgroup scalar_t *destination,
|
| 235 |
+
device const uchar *packed_weight_indices,
|
| 236 |
+
device const float *row_norms,
|
| 237 |
+
device const float *centroids,
|
| 238 |
+
long global_col,
|
| 239 |
+
long global_k,
|
| 240 |
+
long in_features) {
|
| 241 |
+
constexpr uint values = 8;
|
| 242 |
+
constexpr uint byte_count = Bits;
|
| 243 |
+
constexpr uint mask = (1u << Bits) - 1u;
|
| 244 |
+
const long value_offset = global_col * in_features + global_k;
|
| 245 |
+
const long byte_index = (value_offset * Bits) >> 3;
|
| 246 |
+
const float norm = row_norms[global_col];
|
| 247 |
+
|
| 248 |
+
if (Bits == 2) {
|
| 249 |
+
const ushort packed = *reinterpret_cast<device const ushort *>(
|
| 250 |
+
packed_weight_indices + byte_index);
|
| 251 |
+
#pragma clang loop unroll(full)
|
| 252 |
+
for (uint idx = 0; idx < values; ++idx) {
|
| 253 |
+
destination[idx] =
|
| 254 |
+
scalar_t(norm * centroids[(uint(packed) >> (idx * 2)) & 3u]);
|
| 255 |
+
}
|
| 256 |
+
return;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
if (Bits == 3) {
|
| 260 |
+
const PackedMMARead3 bytes = *reinterpret_cast<device const PackedMMARead3 *>(
|
| 261 |
+
packed_weight_indices + byte_index);
|
| 262 |
+
const uint packed =
|
| 263 |
+
uint(bytes.values[0]) | (uint(bytes.values[1]) << 8) |
|
| 264 |
+
(uint(bytes.values[2]) << 16);
|
| 265 |
+
#pragma clang loop unroll(full)
|
| 266 |
+
for (uint idx = 0; idx < values; ++idx) {
|
| 267 |
+
destination[idx] =
|
| 268 |
+
scalar_t(norm * centroids[(packed >> (idx * 3)) & 7u]);
|
| 269 |
+
}
|
| 270 |
+
return;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
if (Bits == 4) {
|
| 274 |
+
const uchar4 packed = *reinterpret_cast<device const uchar4 *>(
|
| 275 |
+
packed_weight_indices + byte_index);
|
| 276 |
+
#pragma clang loop unroll(full)
|
| 277 |
+
for (uint idx = 0; idx < 4; ++idx) {
|
| 278 |
+
const uchar value = packed[idx];
|
| 279 |
+
destination[idx * 2] = scalar_t(norm * centroids[uint(value) & 15u]);
|
| 280 |
+
destination[idx * 2 + 1] =
|
| 281 |
+
scalar_t(norm * centroids[(uint(value) >> 4) & 15u]);
|
| 282 |
+
}
|
| 283 |
+
return;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
uchar packed[byte_count];
|
| 287 |
+
#pragma clang loop unroll(full)
|
| 288 |
+
for (uint idx = 0; idx < byte_count; ++idx) {
|
| 289 |
+
packed[idx] = packed_weight_indices[byte_index + idx];
|
| 290 |
+
}
|
| 291 |
+
#pragma clang loop unroll(full)
|
| 292 |
+
for (uint idx = 0; idx < values; ++idx) {
|
| 293 |
+
const uint bit_start = idx * Bits;
|
| 294 |
+
const uint source_byte = bit_start >> 3;
|
| 295 |
+
const uint shift = bit_start & 7u;
|
| 296 |
+
uint raw = uint(packed[source_byte]);
|
| 297 |
+
if (shift + Bits > 8u) {
|
| 298 |
+
raw |= uint(packed[source_byte + 1]) << 8;
|
| 299 |
+
}
|
| 300 |
+
destination[idx] = scalar_t(norm * centroids[(raw >> shift) & mask]);
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
template <typename scalar_t, uint Bits>
|
| 305 |
+
inline void packed_matmul_padded_mma_value(
|
| 306 |
+
device scalar_t *out,
|
| 307 |
+
device const scalar_t *x,
|
| 308 |
device const uchar *packed_weight_indices,
|
| 309 |
device const float *row_norms,
|
| 310 |
device const float *centroids,
|
| 311 |
device const float *bias,
|
| 312 |
constant PackedMatmulParams ¶ms,
|
| 313 |
+
threadgroup uchar *shared,
|
| 314 |
+
uint2 group_id,
|
| 315 |
+
uint thread_index,
|
| 316 |
+
ushort simdgroup_id,
|
| 317 |
+
ushort lane) {
|
| 318 |
+
threadgroup scalar_t *x_tile = reinterpret_cast<threadgroup scalar_t *>(shared);
|
| 319 |
+
threadgroup scalar_t *weight_tile = x_tile + packed_mma_tile * packed_mma_padded_k;
|
| 320 |
+
const long row_start = long(group_id.y) * packed_mma_tile;
|
| 321 |
+
const long col_start = long(group_id.x) * packed_mma_tile;
|
| 322 |
+
const ushort load_row = ushort(thread_index / 4);
|
| 323 |
+
const ushort load_k = ushort(thread_index % 4) * 8;
|
| 324 |
+
const ushort quad = lane / 4;
|
| 325 |
+
const ushort fragment_row = (quad & 4) + ((lane / 2) % 4);
|
| 326 |
+
const ushort fragment_col = (quad & 2) * 2 + (lane % 2) * 2;
|
| 327 |
+
const ushort simd_row = 8 * (simdgroup_id / 2);
|
| 328 |
+
const ushort simd_col = 8 * (simdgroup_id % 2);
|
|
|
|
| 329 |
|
| 330 |
+
float2 accumulators[2][2];
|
| 331 |
+
#pragma clang loop unroll(full)
|
| 332 |
+
for (ushort matrix_m = 0; matrix_m < 2; ++matrix_m) {
|
| 333 |
+
#pragma clang loop unroll(full)
|
| 334 |
+
for (ushort matrix_n = 0; matrix_n < 2; ++matrix_n) {
|
| 335 |
+
accumulators[matrix_m][matrix_n] = float2(0.0f);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
}
|
| 337 |
+
}
|
| 338 |
|
| 339 |
+
for (long k_start = 0; k_start < params.in_features; k_start += packed_mma_tile) {
|
| 340 |
+
threadgroup_barrier(mem_flags::mem_threadgroup);
|
| 341 |
+
threadgroup scalar_t *x_destination =
|
| 342 |
+
x_tile + load_row * packed_mma_padded_k + load_k;
|
| 343 |
+
if (row_start + long(load_row) < params.rows) {
|
| 344 |
+
*reinterpret_cast<threadgroup PackedMMAReadVector *>(x_destination) =
|
| 345 |
+
*reinterpret_cast<device const PackedMMAReadVector *>(
|
| 346 |
+
x + (row_start + long(load_row)) * params.in_features +
|
| 347 |
+
k_start + long(load_k));
|
| 348 |
+
} else {
|
| 349 |
+
#pragma clang loop unroll(full)
|
| 350 |
+
for (ushort offset = 0; offset < 8; ++offset) {
|
| 351 |
+
x_destination[offset] = scalar_t(0.0f);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
}
|
|
|
|
| 353 |
}
|
| 354 |
+
decode_packed_mma_weight_segment<scalar_t, Bits>(
|
| 355 |
+
weight_tile + load_row * packed_mma_padded_k + load_k,
|
| 356 |
+
packed_weight_indices,
|
| 357 |
+
row_norms,
|
| 358 |
+
centroids,
|
| 359 |
+
col_start + long(load_row),
|
| 360 |
+
k_start + long(load_k),
|
| 361 |
+
params.in_features);
|
| 362 |
threadgroup_barrier(mem_flags::mem_threadgroup);
|
| 363 |
|
| 364 |
+
#pragma clang loop unroll(full)
|
| 365 |
+
for (ushort matrix_k = 0; matrix_k < packed_mma_tile; matrix_k += 8) {
|
| 366 |
+
vec<scalar_t, 2> x_fragments[2];
|
| 367 |
+
vec<scalar_t, 2> weight_fragments[2];
|
| 368 |
+
simdgroup_barrier(mem_flags::mem_none);
|
| 369 |
+
#pragma clang loop unroll(full)
|
| 370 |
+
for (ushort matrix_m = 0; matrix_m < 2; ++matrix_m) {
|
| 371 |
+
threadgroup const scalar_t *source =
|
| 372 |
+
x_tile + (simd_row + fragment_row + matrix_m * 16) *
|
| 373 |
+
packed_mma_padded_k +
|
| 374 |
+
matrix_k + fragment_col;
|
| 375 |
+
x_fragments[matrix_m] = vec<scalar_t, 2>(source[0], source[1]);
|
| 376 |
+
}
|
| 377 |
+
simdgroup_barrier(mem_flags::mem_none);
|
| 378 |
+
#pragma clang loop unroll(full)
|
| 379 |
+
for (ushort matrix_n = 0; matrix_n < 2; ++matrix_n) {
|
| 380 |
+
threadgroup const scalar_t *source =
|
| 381 |
+
weight_tile + (simd_col + fragment_col + matrix_n * 16) *
|
| 382 |
+
packed_mma_padded_k +
|
| 383 |
+
matrix_k + fragment_row;
|
| 384 |
+
weight_fragments[matrix_n] =
|
| 385 |
+
vec<scalar_t, 2>(source[0], source[packed_mma_padded_k]);
|
| 386 |
+
}
|
| 387 |
+
simdgroup_barrier(mem_flags::mem_none);
|
| 388 |
+
#pragma clang loop unroll(full)
|
| 389 |
+
for (ushort matrix_m = 0; matrix_m < 2; ++matrix_m) {
|
| 390 |
+
#pragma clang loop unroll(full)
|
| 391 |
+
for (ushort matrix_n = 0; matrix_n < 2; ++matrix_n) {
|
| 392 |
+
packed_mma_fragment(
|
| 393 |
+
accumulators[matrix_m][matrix_n],
|
| 394 |
+
x_fragments[matrix_m],
|
| 395 |
+
weight_fragments[matrix_n],
|
| 396 |
+
accumulators[matrix_m][matrix_n]);
|
| 397 |
+
}
|
| 398 |
}
|
| 399 |
}
|
|
|
|
|
|
|
| 400 |
}
|
| 401 |
|
| 402 |
+
#pragma clang loop unroll(full)
|
| 403 |
+
for (ushort matrix_m = 0; matrix_m < 2; ++matrix_m) {
|
| 404 |
+
#pragma clang loop unroll(full)
|
| 405 |
+
for (ushort matrix_n = 0; matrix_n < 2; ++matrix_n) {
|
| 406 |
+
const long global_row =
|
| 407 |
+
row_start + long(simd_row + fragment_row + matrix_m * 16);
|
| 408 |
+
const long global_col =
|
| 409 |
+
col_start + long(simd_col + fragment_col + matrix_n * 16);
|
| 410 |
+
float2 values = accumulators[matrix_m][matrix_n];
|
| 411 |
+
if (params.has_bias != 0) {
|
| 412 |
+
values += float2(bias[global_col], bias[global_col + 1]);
|
| 413 |
+
}
|
| 414 |
+
if (global_row < params.rows) {
|
| 415 |
+
out[global_row * params.out_features + global_col] = scalar_t(values[0]);
|
| 416 |
+
out[global_row * params.out_features + global_col + 1] = scalar_t(values[1]);
|
| 417 |
+
}
|
| 418 |
+
}
|
| 419 |
}
|
| 420 |
}
|
| 421 |
|
|
|
|
| 446 |
}
|
| 447 |
|
| 448 |
kernel void packed_matmul_forward_half(
|
| 449 |
+
device half *out [[buffer(0)]],
|
| 450 |
+
device const half *x [[buffer(1)]],
|
| 451 |
+
device const uchar *packed_weight_indices [[buffer(2)]],
|
| 452 |
+
device const float *row_norms [[buffer(3)]],
|
| 453 |
+
device const float *centroids [[buffer(4)]],
|
| 454 |
+
device const float *bias [[buffer(5)]],
|
| 455 |
+
constant PackedMatmulParams ¶ms [[buffer(6)]],
|
| 456 |
+
threadgroup uchar *shared [[threadgroup(0)]],
|
| 457 |
+
uint2 group_id [[threadgroup_position_in_grid]],
|
| 458 |
+
uint thread_index [[thread_index_in_threadgroup]],
|
| 459 |
+
ushort simdgroup_id [[simdgroup_index_in_threadgroup]],
|
| 460 |
+
ushort lane [[thread_index_in_simdgroup]]) {
|
| 461 |
+
#define ORBITQUANT_PACKED_MMA_HALF(BITS_VALUE) \
|
| 462 |
+
packed_matmul_padded_mma_value<half, BITS_VALUE>( \
|
| 463 |
+
out, x, packed_weight_indices, row_norms, centroids, bias, params, \
|
| 464 |
+
shared, group_id, thread_index, simdgroup_id, lane)
|
| 465 |
+
switch (params.bits) {
|
| 466 |
+
case 2:
|
| 467 |
+
ORBITQUANT_PACKED_MMA_HALF(2);
|
| 468 |
+
break;
|
| 469 |
+
case 3:
|
| 470 |
+
ORBITQUANT_PACKED_MMA_HALF(3);
|
| 471 |
+
break;
|
| 472 |
+
case 4:
|
| 473 |
+
ORBITQUANT_PACKED_MMA_HALF(4);
|
| 474 |
+
break;
|
| 475 |
+
case 6:
|
| 476 |
+
ORBITQUANT_PACKED_MMA_HALF(6);
|
| 477 |
+
break;
|
| 478 |
+
}
|
| 479 |
+
#undef ORBITQUANT_PACKED_MMA_HALF
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
kernel void packed_matmul_forward_bfloat16(
|
| 483 |
+
device bfloat *out [[buffer(0)]],
|
| 484 |
+
device const bfloat *x [[buffer(1)]],
|
| 485 |
+
device const uchar *packed_weight_indices [[buffer(2)]],
|
| 486 |
+
device const float *row_norms [[buffer(3)]],
|
| 487 |
+
device const float *centroids [[buffer(4)]],
|
| 488 |
+
device const float *bias [[buffer(5)]],
|
| 489 |
+
constant PackedMatmulParams ¶ms [[buffer(6)]],
|
| 490 |
+
threadgroup uchar *shared [[threadgroup(0)]],
|
| 491 |
+
uint2 group_id [[threadgroup_position_in_grid]],
|
| 492 |
+
uint thread_index [[thread_index_in_threadgroup]],
|
| 493 |
+
ushort simdgroup_id [[simdgroup_index_in_threadgroup]],
|
| 494 |
+
ushort lane [[thread_index_in_simdgroup]]) {
|
| 495 |
+
#define ORBITQUANT_PACKED_MMA_BFLOAT16(BITS_VALUE) \
|
| 496 |
+
packed_matmul_padded_mma_value<bfloat, BITS_VALUE>( \
|
| 497 |
+
out, x, packed_weight_indices, row_norms, centroids, bias, params, \
|
| 498 |
+
shared, group_id, thread_index, simdgroup_id, lane)
|
| 499 |
+
switch (params.bits) {
|
| 500 |
+
case 2:
|
| 501 |
+
ORBITQUANT_PACKED_MMA_BFLOAT16(2);
|
| 502 |
+
break;
|
| 503 |
+
case 3:
|
| 504 |
+
ORBITQUANT_PACKED_MMA_BFLOAT16(3);
|
| 505 |
+
break;
|
| 506 |
+
case 4:
|
| 507 |
+
ORBITQUANT_PACKED_MMA_BFLOAT16(4);
|
| 508 |
+
break;
|
| 509 |
+
case 6:
|
| 510 |
+
ORBITQUANT_PACKED_MMA_BFLOAT16(6);
|
| 511 |
+
break;
|
| 512 |
+
}
|
| 513 |
+
#undef ORBITQUANT_PACKED_MMA_BFLOAT16
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
kernel void packed_matmul_forward_half_scalar(
|
| 517 |
device half *out [[buffer(0)]],
|
| 518 |
device const half *x [[buffer(1)]],
|
| 519 |
device const uchar *packed_weight_indices [[buffer(2)]],
|
|
|
|
| 539 |
threads_per_group);
|
| 540 |
}
|
| 541 |
|
| 542 |
+
kernel void packed_matmul_forward_bfloat16_scalar(
|
| 543 |
+
device bfloat *out [[buffer(0)]],
|
| 544 |
+
device const bfloat *x [[buffer(1)]],
|
| 545 |
device const uchar *packed_weight_indices [[buffer(2)]],
|
| 546 |
device const float *row_norms [[buffer(3)]],
|
| 547 |
device const float *centroids [[buffer(4)]],
|
|
|
|
| 551 |
uint2 tid [[thread_position_in_grid]],
|
| 552 |
uint2 local_tid [[thread_position_in_threadgroup]],
|
| 553 |
uint2 threads_per_group [[threads_per_threadgroup]]) {
|
| 554 |
+
packed_matmul_tiled_value(
|
| 555 |
out,
|
| 556 |
x,
|
| 557 |
packed_weight_indices,
|
|
|
|
| 564 |
local_tid,
|
| 565 |
threads_per_group);
|
| 566 |
}
|
| 567 |
+
|
| 568 |
+
#define ORBITQUANT_PACKED_SMALL_ROWS_KERNEL(NAME, TYPE) \
|
| 569 |
+
kernel void NAME( \
|
| 570 |
+
device TYPE *out [[buffer(0)]], \
|
| 571 |
+
device const TYPE *x [[buffer(1)]], \
|
| 572 |
+
device const uchar *packed_weight_indices [[buffer(2)]], \
|
| 573 |
+
device const float *row_norms [[buffer(3)]], \
|
| 574 |
+
device const float *centroids [[buffer(4)]], \
|
| 575 |
+
device const float *bias [[buffer(5)]], \
|
| 576 |
+
constant PackedMatmulParams ¶ms [[buffer(6)]], \
|
| 577 |
+
uint2 group_id [[threadgroup_position_in_grid]], \
|
| 578 |
+
uint thread_index [[thread_index_in_threadgroup]], \
|
| 579 |
+
ushort simdgroup_id [[simdgroup_index_in_threadgroup]], \
|
| 580 |
+
ushort lane [[thread_index_in_simdgroup]]) { \
|
| 581 |
+
threadgroup float centroid_lut[64]; \
|
| 582 |
+
switch (params.bits) { \
|
| 583 |
+
case 2: \
|
| 584 |
+
packed_matmul_gemv_value<TYPE, 2>( \
|
| 585 |
+
out, x, packed_weight_indices, row_norms, centroids, bias, \
|
| 586 |
+
params, centroid_lut, group_id, thread_index, simdgroup_id, \
|
| 587 |
+
lane); \
|
| 588 |
+
break; \
|
| 589 |
+
case 3: \
|
| 590 |
+
packed_matmul_gemv_value<TYPE, 3>( \
|
| 591 |
+
out, x, packed_weight_indices, row_norms, centroids, bias, \
|
| 592 |
+
params, centroid_lut, group_id, thread_index, simdgroup_id, \
|
| 593 |
+
lane); \
|
| 594 |
+
break; \
|
| 595 |
+
case 4: \
|
| 596 |
+
packed_matmul_gemv_value<TYPE, 4>( \
|
| 597 |
+
out, x, packed_weight_indices, row_norms, centroids, bias, \
|
| 598 |
+
params, centroid_lut, group_id, thread_index, simdgroup_id, \
|
| 599 |
+
lane); \
|
| 600 |
+
break; \
|
| 601 |
+
case 6: \
|
| 602 |
+
packed_matmul_gemv_value<TYPE, 6>( \
|
| 603 |
+
out, x, packed_weight_indices, row_norms, centroids, bias, \
|
| 604 |
+
params, centroid_lut, group_id, thread_index, simdgroup_id, \
|
| 605 |
+
lane); \
|
| 606 |
+
break; \
|
| 607 |
+
} \
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
ORBITQUANT_PACKED_SMALL_ROWS_KERNEL(packed_matmul_forward_float_small_rows, float)
|
| 611 |
+
ORBITQUANT_PACKED_SMALL_ROWS_KERNEL(packed_matmul_forward_half_small_rows, half)
|
| 612 |
+
ORBITQUANT_PACKED_SMALL_ROWS_KERNEL(packed_matmul_forward_bfloat16_small_rows, bfloat)
|
| 613 |
+
|
| 614 |
+
#undef ORBITQUANT_PACKED_SMALL_ROWS_KERNEL
|
orbitquant_packed_matmul_metal/packed_matmul.mm
CHANGED
|
@@ -33,6 +33,11 @@ struct PackedMatmulPipelineCache {
|
|
| 33 |
id<MTLComputePipelineState> float_pipeline;
|
| 34 |
id<MTLComputePipelineState> half_pipeline;
|
| 35 |
id<MTLComputePipelineState> bfloat16_pipeline;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
};
|
| 37 |
|
| 38 |
static id<MTLComputePipelineState> create_pipeline(
|
|
@@ -68,6 +73,16 @@ static PackedMatmulPipelineCache &packed_matmul_pipeline_cache() {
|
|
| 68 |
create_pipeline(cache.device, library, "packed_matmul_forward_half");
|
| 69 |
cache.bfloat16_pipeline =
|
| 70 |
create_pipeline(cache.device, library, "packed_matmul_forward_bfloat16");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
});
|
| 73 |
return cache;
|
|
@@ -75,14 +90,25 @@ static PackedMatmulPipelineCache &packed_matmul_pipeline_cache() {
|
|
| 75 |
|
| 76 |
static id<MTLComputePipelineState> select_packed_matmul_pipeline(
|
| 77 |
PackedMatmulPipelineCache &cache,
|
| 78 |
-
c10::ScalarType dtype
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
if (dtype == torch::kFloat) {
|
| 80 |
return cache.float_pipeline;
|
| 81 |
}
|
| 82 |
if (dtype == torch::kHalf) {
|
| 83 |
-
return cache.half_pipeline;
|
| 84 |
}
|
| 85 |
-
return cache.bfloat16_pipeline;
|
| 86 |
}
|
| 87 |
|
| 88 |
static void dispatch_packed_matmul_kernel(
|
|
@@ -100,9 +126,21 @@ static void dispatch_packed_matmul_kernel(
|
|
| 100 |
int64_t block_n,
|
| 101 |
int64_t block_k) {
|
| 102 |
@autoreleasepool {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
PackedMatmulPipelineCache &cache = packed_matmul_pipeline_cache();
|
| 104 |
id<MTLComputePipelineState> pipeline =
|
| 105 |
-
select_packed_matmul_pipeline(
|
|
|
|
| 106 |
|
| 107 |
id<MTLCommandBuffer> command_buffer = torch::mps::get_command_buffer();
|
| 108 |
TORCH_CHECK(command_buffer, "Failed to retrieve MPS command buffer");
|
|
@@ -141,14 +179,54 @@ static void dispatch_packed_matmul_kernel(
|
|
| 141 |
atIndex:5];
|
| 142 |
[encoder setBytes:¶ms length:sizeof(params) atIndex:6];
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
[encoder endEncoding];
|
| 153 |
torch::mps::commit();
|
| 154 |
});
|
|
|
|
| 33 |
id<MTLComputePipelineState> float_pipeline;
|
| 34 |
id<MTLComputePipelineState> half_pipeline;
|
| 35 |
id<MTLComputePipelineState> bfloat16_pipeline;
|
| 36 |
+
id<MTLComputePipelineState> half_scalar_pipeline;
|
| 37 |
+
id<MTLComputePipelineState> bfloat16_scalar_pipeline;
|
| 38 |
+
id<MTLComputePipelineState> float_small_rows_pipeline;
|
| 39 |
+
id<MTLComputePipelineState> half_small_rows_pipeline;
|
| 40 |
+
id<MTLComputePipelineState> bfloat16_small_rows_pipeline;
|
| 41 |
};
|
| 42 |
|
| 43 |
static id<MTLComputePipelineState> create_pipeline(
|
|
|
|
| 73 |
create_pipeline(cache.device, library, "packed_matmul_forward_half");
|
| 74 |
cache.bfloat16_pipeline =
|
| 75 |
create_pipeline(cache.device, library, "packed_matmul_forward_bfloat16");
|
| 76 |
+
cache.half_scalar_pipeline =
|
| 77 |
+
create_pipeline(cache.device, library, "packed_matmul_forward_half_scalar");
|
| 78 |
+
cache.bfloat16_scalar_pipeline =
|
| 79 |
+
create_pipeline(cache.device, library, "packed_matmul_forward_bfloat16_scalar");
|
| 80 |
+
cache.float_small_rows_pipeline =
|
| 81 |
+
create_pipeline(cache.device, library, "packed_matmul_forward_float_small_rows");
|
| 82 |
+
cache.half_small_rows_pipeline =
|
| 83 |
+
create_pipeline(cache.device, library, "packed_matmul_forward_half_small_rows");
|
| 84 |
+
cache.bfloat16_small_rows_pipeline = create_pipeline(
|
| 85 |
+
cache.device, library, "packed_matmul_forward_bfloat16_small_rows");
|
| 86 |
}
|
| 87 |
});
|
| 88 |
return cache;
|
|
|
|
| 90 |
|
| 91 |
static id<MTLComputePipelineState> select_packed_matmul_pipeline(
|
| 92 |
PackedMatmulPipelineCache &cache,
|
| 93 |
+
c10::ScalarType dtype,
|
| 94 |
+
bool use_padded_mma,
|
| 95 |
+
bool use_small_rows) {
|
| 96 |
+
if (use_small_rows) {
|
| 97 |
+
if (dtype == torch::kFloat) {
|
| 98 |
+
return cache.float_small_rows_pipeline;
|
| 99 |
+
}
|
| 100 |
+
if (dtype == torch::kHalf) {
|
| 101 |
+
return cache.half_small_rows_pipeline;
|
| 102 |
+
}
|
| 103 |
+
return cache.bfloat16_small_rows_pipeline;
|
| 104 |
+
}
|
| 105 |
if (dtype == torch::kFloat) {
|
| 106 |
return cache.float_pipeline;
|
| 107 |
}
|
| 108 |
if (dtype == torch::kHalf) {
|
| 109 |
+
return use_padded_mma ? cache.half_pipeline : cache.half_scalar_pipeline;
|
| 110 |
}
|
| 111 |
+
return use_padded_mma ? cache.bfloat16_pipeline : cache.bfloat16_scalar_pipeline;
|
| 112 |
}
|
| 113 |
|
| 114 |
static void dispatch_packed_matmul_kernel(
|
|
|
|
| 126 |
int64_t block_n,
|
| 127 |
int64_t block_k) {
|
| 128 |
@autoreleasepool {
|
| 129 |
+
// Skinny batches take the GEMV path even when the MMA tiles would fit:
|
| 130 |
+
// below 5 rows the 32-row MMA tile wastes most of its work, while the
|
| 131 |
+
// GEMV path reuses each decoded weight segment across the whole batch.
|
| 132 |
+
// From 5 rows up the measured crossover already favors the MMA tiles.
|
| 133 |
+
const bool use_small_rows = x.size(0) <= 4 &&
|
| 134 |
+
(bits == 2 || bits == 3 || bits == 4 || bits == 6) &&
|
| 135 |
+
in_features % 8 == 0;
|
| 136 |
+
const bool use_padded_mma = !use_small_rows &&
|
| 137 |
+
x.scalar_type() != torch::kFloat && x.size(0) > 1 &&
|
| 138 |
+
out_features % 32 == 0 && in_features % 32 == 0 &&
|
| 139 |
+
(bits == 2 || bits == 3 || bits == 4 || bits == 6);
|
| 140 |
PackedMatmulPipelineCache &cache = packed_matmul_pipeline_cache();
|
| 141 |
id<MTLComputePipelineState> pipeline =
|
| 142 |
+
select_packed_matmul_pipeline(
|
| 143 |
+
cache, x.scalar_type(), use_padded_mma, use_small_rows);
|
| 144 |
|
| 145 |
id<MTLCommandBuffer> command_buffer = torch::mps::get_command_buffer();
|
| 146 |
TORCH_CHECK(command_buffer, "Failed to retrieve MPS command buffer");
|
|
|
|
| 179 |
atIndex:5];
|
| 180 |
[encoder setBytes:¶ms length:sizeof(params) atIndex:6];
|
| 181 |
|
| 182 |
+
if (use_small_rows) {
|
| 183 |
+
constexpr NSUInteger columns_per_threadgroup = 8;
|
| 184 |
+
constexpr NSUInteger threads = 8 * 32;
|
| 185 |
+
MTLSize threadgroups = MTLSizeMake(
|
| 186 |
+
(out_features + columns_per_threadgroup - 1) /
|
| 187 |
+
columns_per_threadgroup,
|
| 188 |
+
1,
|
| 189 |
+
1);
|
| 190 |
+
MTLSize threadgroup_size = MTLSizeMake(threads, 1, 1);
|
| 191 |
+
[encoder dispatchThreadgroups:threadgroups
|
| 192 |
+
threadsPerThreadgroup:threadgroup_size];
|
| 193 |
+
} else if (!use_padded_mma) {
|
| 194 |
+
const NSUInteger threads_x =
|
| 195 |
+
std::min<int64_t>(std::max<int64_t>(block_n, 1), 32);
|
| 196 |
+
const NSUInteger threads_y = std::min<int64_t>(
|
| 197 |
+
x.size(0), std::min<int64_t>(std::max<int64_t>(block_m, 1), 32));
|
| 198 |
+
const NSUInteger tile_k =
|
| 199 |
+
std::min<int64_t>(std::max<int64_t>(block_k, 1), 128);
|
| 200 |
+
const NSUInteger shared_bytes =
|
| 201 |
+
(threads_y * tile_k + tile_k * threads_x) * sizeof(float);
|
| 202 |
+
[encoder setThreadgroupMemoryLength:shared_bytes atIndex:0];
|
| 203 |
+
MTLSize grid_size = MTLSizeMake(out_features, x.size(0), 1);
|
| 204 |
+
MTLSize threadgroup_size = MTLSizeMake(threads_x, threads_y, 1);
|
| 205 |
+
[encoder dispatchThreads:grid_size threadsPerThreadgroup:threadgroup_size];
|
| 206 |
+
} else {
|
| 207 |
+
constexpr NSUInteger tile_m = 32;
|
| 208 |
+
constexpr NSUInteger tile_n = 32;
|
| 209 |
+
constexpr NSUInteger padded_k = 40;
|
| 210 |
+
constexpr NSUInteger threads = 128;
|
| 211 |
+
const NSUInteger scalar_bytes = x.element_size();
|
| 212 |
+
const NSUInteger shared_bytes =
|
| 213 |
+
2 * tile_m * padded_k * scalar_bytes;
|
| 214 |
+
TORCH_CHECK(
|
| 215 |
+
threads <= pipeline.maxTotalThreadsPerThreadgroup,
|
| 216 |
+
"Metal packed matmul pipeline supports only ",
|
| 217 |
+
pipeline.maxTotalThreadsPerThreadgroup,
|
| 218 |
+
" threads per threadgroup, but ",
|
| 219 |
+
threads,
|
| 220 |
+
" are required");
|
| 221 |
+
[encoder setThreadgroupMemoryLength:shared_bytes atIndex:0];
|
| 222 |
+
MTLSize threadgroups = MTLSizeMake(
|
| 223 |
+
(out_features + tile_n - 1) / tile_n,
|
| 224 |
+
(x.size(0) + tile_m - 1) / tile_m,
|
| 225 |
+
1);
|
| 226 |
+
MTLSize threadgroup_size = MTLSizeMake(threads, 1, 1);
|
| 227 |
+
[encoder dispatchThreadgroups:threadgroups
|
| 228 |
+
threadsPerThreadgroup:threadgroup_size];
|
| 229 |
+
}
|
| 230 |
[encoder endEncoding];
|
| 231 |
torch::mps::commit();
|
| 232 |
});
|
scripts/prepare_wheel_project.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def main() -> None:
|
| 8 |
+
parser = argparse.ArgumentParser(
|
| 9 |
+
description="Prepare kernel-builder output for a platform wheel build."
|
| 10 |
+
)
|
| 11 |
+
parser.add_argument("project", type=Path)
|
| 12 |
+
parser.add_argument("--version", required=True)
|
| 13 |
+
parser.add_argument(
|
| 14 |
+
"--torch-requirement",
|
| 15 |
+
default="torch>=2.11",
|
| 16 |
+
help=(
|
| 17 |
+
"torch dependency for the wheel metadata; non-stable-ABI variants "
|
| 18 |
+
'must pin the torch minor they were built against (e.g. "torch>=2.9,<2.10")'
|
| 19 |
+
),
|
| 20 |
+
)
|
| 21 |
+
args = parser.parse_args()
|
| 22 |
+
|
| 23 |
+
pyproject = args.project / "pyproject.toml"
|
| 24 |
+
text = pyproject.read_text(encoding="utf-8")
|
| 25 |
+
|
| 26 |
+
old_version = 'version = "0.1.0"'
|
| 27 |
+
if text.count(old_version) != 1:
|
| 28 |
+
raise RuntimeError("generated pyproject must contain one stub version")
|
| 29 |
+
text = text.replace(old_version, f'version = "{args.version}"', 1)
|
| 30 |
+
|
| 31 |
+
requires_python = 'requires-python = ">=3.9"'
|
| 32 |
+
if text.count(requires_python) != 1:
|
| 33 |
+
raise RuntimeError("generated pyproject must contain one Python requirement")
|
| 34 |
+
text = text.replace(
|
| 35 |
+
requires_python,
|
| 36 |
+
f'{requires_python}\ndependencies = ["{args.torch_requirement}"]',
|
| 37 |
+
1,
|
| 38 |
+
)
|
| 39 |
+
pyproject.write_text(text, encoding="utf-8")
|
| 40 |
+
|
| 41 |
+
cmake = args.project / "CMakeLists.txt"
|
| 42 |
+
cmake_text = cmake.read_text(encoding="utf-8")
|
| 43 |
+
for required in (False, True):
|
| 44 |
+
marker = " REQUIRED" if required else ""
|
| 45 |
+
development = (
|
| 46 |
+
f"find_package(Python3{marker} COMPONENTS Development "
|
| 47 |
+
"Development.SABIModule Interpreter)"
|
| 48 |
+
)
|
| 49 |
+
if cmake_text.count(development) != 1:
|
| 50 |
+
raise RuntimeError(
|
| 51 |
+
"generated CMake must contain one Python development lookup"
|
| 52 |
+
)
|
| 53 |
+
cmake_text = cmake_text.replace(
|
| 54 |
+
development,
|
| 55 |
+
f"find_package(Python3{marker} COMPONENTS Development.SABIModule Interpreter)",
|
| 56 |
+
1,
|
| 57 |
+
)
|
| 58 |
+
cmake.write_text(cmake_text, encoding="utf-8")
|
| 59 |
+
|
| 60 |
+
setup = args.project / "setup.py"
|
| 61 |
+
setup_text = setup.read_text(encoding="utf-8")
|
| 62 |
+
shutil_import = "from shutil import which, move\n"
|
| 63 |
+
if setup_text.count(shutil_import) != 1:
|
| 64 |
+
raise RuntimeError("generated setup must contain one shutil import")
|
| 65 |
+
setup_text = setup_text.replace(
|
| 66 |
+
shutil_import,
|
| 67 |
+
"from shutil import copy2, move, which\n",
|
| 68 |
+
1,
|
| 69 |
+
)
|
| 70 |
+
ninja_path = 'ninja_executable_path = Path(ninja.BIN_DIR) / "ninja"'
|
| 71 |
+
if setup_text.count(ninja_path) != 1:
|
| 72 |
+
raise RuntimeError("generated setup must contain one Ninja executable path")
|
| 73 |
+
setup_text = setup_text.replace(
|
| 74 |
+
ninja_path,
|
| 75 |
+
'ninja_executable_path = Path(ninja.BIN_DIR) / '
|
| 76 |
+
'("ninja.exe" if os.name == "nt" else "ninja")',
|
| 77 |
+
1,
|
| 78 |
+
)
|
| 79 |
+
for cache_tool in ("sccache", "ccache"):
|
| 80 |
+
availability = f'return which("{cache_tool}") is not None'
|
| 81 |
+
if setup_text.count(availability) != 1:
|
| 82 |
+
raise RuntimeError(
|
| 83 |
+
f"generated setup must contain one {cache_tool} availability check"
|
| 84 |
+
)
|
| 85 |
+
setup_text = setup_text.replace(
|
| 86 |
+
availability,
|
| 87 |
+
f'return os.name != "nt" and which("{cache_tool}") is not None',
|
| 88 |
+
1,
|
| 89 |
+
)
|
| 90 |
+
cmake_args_hook = (
|
| 91 |
+
' if "CMAKE_ARGS" in os.environ:\n'
|
| 92 |
+
' cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") '
|
| 93 |
+
"if item]\n"
|
| 94 |
+
)
|
| 95 |
+
if setup_text.count(cmake_args_hook) != 1:
|
| 96 |
+
raise RuntimeError("generated setup must contain one CMAKE_ARGS hook")
|
| 97 |
+
setup_text = setup_text.replace(
|
| 98 |
+
cmake_args_hook,
|
| 99 |
+
cmake_args_hook
|
| 100 |
+
+ ' cmake_make_program = os.environ.get("ORBITQUANT_CMAKE_MAKE_PROGRAM")\n'
|
| 101 |
+
+ " if cmake_make_program:\n"
|
| 102 |
+
+ ' cmake_args.append(f"-DCMAKE_MAKE_PROGRAM:FILEPATH={cmake_make_program}")\n',
|
| 103 |
+
1,
|
| 104 |
+
)
|
| 105 |
+
build_temp = " build_temp = Path(self.build_temp) / ext.name"
|
| 106 |
+
if setup_text.count(build_temp) != 1:
|
| 107 |
+
raise RuntimeError("generated setup must contain one extension build temp")
|
| 108 |
+
setup_text = setup_text.replace(
|
| 109 |
+
build_temp,
|
| 110 |
+
' build_temp_root = os.environ.get("ORBITQUANT_BUILD_TEMP", '
|
| 111 |
+
"self.build_temp)\n"
|
| 112 |
+
" build_temp = (Path(build_temp_root) / ext.name).resolve()",
|
| 113 |
+
1,
|
| 114 |
+
)
|
| 115 |
+
windows_multi_config = (
|
| 116 |
+
' if sys.platform == "win32":\n'
|
| 117 |
+
" # Move the dylib one folder up for discovery."
|
| 118 |
+
)
|
| 119 |
+
if setup_text.count(windows_multi_config) != 1:
|
| 120 |
+
raise RuntimeError("generated setup must contain one Windows output move")
|
| 121 |
+
setup_text = setup_text.replace(
|
| 122 |
+
windows_multi_config,
|
| 123 |
+
' if sys.platform == "win32" and (extdir / cfg).is_dir():\n'
|
| 124 |
+
" # Move the dylib one folder up for discovery.",
|
| 125 |
+
1,
|
| 126 |
+
)
|
| 127 |
+
build_call = (
|
| 128 |
+
" subprocess.run(\n"
|
| 129 |
+
' ["cmake", "--build", str(build_temp), *build_args], '
|
| 130 |
+
"cwd=build_temp, check=True\n"
|
| 131 |
+
" )\n"
|
| 132 |
+
)
|
| 133 |
+
if setup_text.count(build_call) != 1:
|
| 134 |
+
raise RuntimeError("generated setup must contain one wheel CMake build call")
|
| 135 |
+
setup_text = setup_text.replace(
|
| 136 |
+
build_call,
|
| 137 |
+
build_call
|
| 138 |
+
+ "\n"
|
| 139 |
+
+ ' package_name = ext.name.split(".", 1)[0]\n'
|
| 140 |
+
+ " generated_ops = (\n"
|
| 141 |
+
+ ' Path(ext.sourcedir) / "torch-ext" / package_name / "_ops.py"\n'
|
| 142 |
+
+ " )\n"
|
| 143 |
+
+ ' copy2(generated_ops, extdir / "_ops.py")\n',
|
| 144 |
+
1,
|
| 145 |
+
)
|
| 146 |
+
zip_safe = " zip_safe=False,\n"
|
| 147 |
+
if setup_text.count(zip_safe) != 1:
|
| 148 |
+
raise RuntimeError("generated setup must contain one zip-safe option")
|
| 149 |
+
setup_text = setup_text.replace(
|
| 150 |
+
zip_safe,
|
| 151 |
+
' options={"bdist_wheel": {"py_limited_api": "cp39"}},\n' + zip_safe,
|
| 152 |
+
1,
|
| 153 |
+
)
|
| 154 |
+
setup.write_text(setup_text, encoding="utf-8")
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
if __name__ == "__main__":
|
| 158 |
+
main()
|
tests/test_packed_matmul.py
CHANGED
|
@@ -1,8 +1,20 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
|
|
|
| 3 |
import pytest
|
| 4 |
import torch
|
| 5 |
-
from orbitquant_packed_matmul import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
|
| 8 |
def _pack(values: torch.Tensor, bits: int) -> torch.Tensor:
|
|
@@ -19,15 +31,17 @@ def _pack(values: torch.Tensor, bits: int) -> torch.Tensor:
|
|
| 19 |
|
| 20 |
|
| 21 |
def _device() -> str:
|
| 22 |
-
if torch.cuda.is_available():
|
| 23 |
return "cuda"
|
| 24 |
-
if torch.backends.mps.is_available():
|
| 25 |
return "mps"
|
| 26 |
-
|
|
|
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
def _mps_device() -> str:
|
| 30 |
-
if not torch.backends.mps.is_available():
|
| 31 |
pytest.skip("MPS is required")
|
| 32 |
return "mps"
|
| 33 |
|
|
@@ -41,6 +55,313 @@ def _mps_bfloat16_device() -> str:
|
|
| 41 |
return device
|
| 42 |
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
@pytest.mark.kernels_ci
|
| 45 |
@pytest.mark.parametrize("bits", [2, 3, 4, 6])
|
| 46 |
@pytest.mark.parametrize("in_features", [16, 19])
|
|
@@ -53,14 +374,11 @@ def test_matmul_packed_weight_matches_dequantized_reference(
|
|
| 53 |
rows = 9
|
| 54 |
out_features = 7
|
| 55 |
x = torch.randn(rows, in_features, device=device, dtype=dtype)
|
| 56 |
-
indices = (
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
)
|
| 60 |
-
% (2**bits)
|
| 61 |
-
)
|
| 62 |
packed = _pack(indices, bits).to(device)
|
| 63 |
-
row_norms =
|
| 64 |
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 65 |
bias = torch.randn(out_features, device=device, dtype=dtype) if with_bias else None
|
| 66 |
|
|
@@ -87,6 +405,53 @@ def test_matmul_packed_weight_matches_dequantized_reference(
|
|
| 87 |
assert torch.allclose(actual.float().cpu(), expected, atol=2e-2, rtol=2e-2)
|
| 88 |
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
@pytest.mark.kernels_ci
|
| 91 |
def test_matmul_packed_weight_explicit_mps_path_matches_dequantized_reference() -> None:
|
| 92 |
device = _mps_device()
|
|
@@ -95,14 +460,11 @@ def test_matmul_packed_weight_explicit_mps_path_matches_dequantized_reference()
|
|
| 95 |
in_features = 19
|
| 96 |
out_features = 7
|
| 97 |
x = torch.randn(rows, in_features, device=device, dtype=torch.float16)
|
| 98 |
-
indices = (
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
)
|
| 102 |
-
% (2**bits)
|
| 103 |
-
)
|
| 104 |
packed = _pack(indices, bits).to(device)
|
| 105 |
-
row_norms =
|
| 106 |
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 107 |
bias = torch.randn(out_features, device=device, dtype=torch.float16)
|
| 108 |
|
|
@@ -136,14 +498,11 @@ def test_matmul_packed_weight_explicit_mps_bfloat16_path_matches_dequantized_ref
|
|
| 136 |
in_features = 19
|
| 137 |
out_features = 7
|
| 138 |
x = torch.randn(rows, in_features, device=device, dtype=torch.bfloat16)
|
| 139 |
-
indices = (
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
)
|
| 143 |
-
% (2**bits)
|
| 144 |
-
)
|
| 145 |
packed = _pack(indices, bits).to(device)
|
| 146 |
-
row_norms =
|
| 147 |
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 148 |
bias = torch.randn(out_features, device=device, dtype=torch.bfloat16)
|
| 149 |
|
|
@@ -167,3 +526,344 @@ def test_matmul_packed_weight_explicit_mps_bfloat16_path_matches_dequantized_ref
|
|
| 167 |
assert actual.dtype == torch.bfloat16
|
| 168 |
assert actual.shape == (rows, out_features)
|
| 169 |
assert torch.allclose(actual.float().cpu(), expected, atol=3e-2, rtol=3e-2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
import platform
|
| 4 |
+
|
| 5 |
import pytest
|
| 6 |
import torch
|
| 7 |
+
from orbitquant_packed_matmul import (
|
| 8 |
+
matmul_packed_adaln_int4_cpu,
|
| 9 |
+
matmul_packed_w4a4_int8,
|
| 10 |
+
matmul_packed_weight,
|
| 11 |
+
quantize_activations_cpu,
|
| 12 |
+
quantize_activations_int8,
|
| 13 |
+
quantize_activations_packed_w4,
|
| 14 |
+
supports_cpu_activation,
|
| 15 |
+
supports_cpu_adaln,
|
| 16 |
+
supports_device,
|
| 17 |
+
)
|
| 18 |
|
| 19 |
|
| 20 |
def _pack(values: torch.Tensor, bits: int) -> torch.Tensor:
|
|
|
|
| 31 |
|
| 32 |
|
| 33 |
def _device() -> str:
|
| 34 |
+
if supports_device("cuda") and torch.cuda.is_available():
|
| 35 |
return "cuda"
|
| 36 |
+
if supports_device("mps") and torch.backends.mps.is_available():
|
| 37 |
return "mps"
|
| 38 |
+
if supports_device("cpu"):
|
| 39 |
+
return "cpu"
|
| 40 |
+
pytest.skip("the built variant has no runnable backend")
|
| 41 |
|
| 42 |
|
| 43 |
def _mps_device() -> str:
|
| 44 |
+
if not supports_device("mps") or not torch.backends.mps.is_available():
|
| 45 |
pytest.skip("MPS is required")
|
| 46 |
return "mps"
|
| 47 |
|
|
|
|
| 55 |
return device
|
| 56 |
|
| 57 |
|
| 58 |
+
def _cuda_device() -> str:
|
| 59 |
+
if not supports_device("cuda") or not torch.cuda.is_available():
|
| 60 |
+
pytest.skip("CUDA is required")
|
| 61 |
+
return "cuda"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _runnable_cpu_isas() -> list[str]:
|
| 65 |
+
machine = platform.machine().lower()
|
| 66 |
+
capability = torch.backends.cpu.get_cpu_capability().upper()
|
| 67 |
+
isas = ["scalar"]
|
| 68 |
+
if machine in {"x86_64", "amd64"} and capability in {"AVX2", "AVX512"}:
|
| 69 |
+
isas.append("avx2")
|
| 70 |
+
if machine in {"x86_64", "amd64"} and capability == "AVX512" and platform.system() != "Windows":
|
| 71 |
+
# The MSVC wheel currently ships the separately compiled AVX2 TU; the
|
| 72 |
+
# AVX-512 implementation uses GCC/Clang per-function target attributes.
|
| 73 |
+
isas.append("avx512")
|
| 74 |
+
if machine in {"aarch64", "arm64"}:
|
| 75 |
+
isas.append("neon")
|
| 76 |
+
return isas
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _row_norms(device: str, out_features: int) -> torch.Tensor:
|
| 80 |
+
dtype = torch.bfloat16 if device == "cuda" else torch.float32
|
| 81 |
+
return torch.linspace(0.5, 1.5, out_features, device=device, dtype=dtype)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _fwht_reference(values: torch.Tensor) -> torch.Tensor:
|
| 85 |
+
output = values.clone()
|
| 86 |
+
half = 1
|
| 87 |
+
while half < output.shape[-1]:
|
| 88 |
+
blocks = output.reshape(*output.shape[:-1], -1, 2 * half)
|
| 89 |
+
left = blocks[..., :half].clone()
|
| 90 |
+
right = blocks[..., half:].clone()
|
| 91 |
+
blocks[..., :half] = left + right
|
| 92 |
+
blocks[..., half:] = left - right
|
| 93 |
+
half *= 2
|
| 94 |
+
return output
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
@pytest.mark.kernels_ci
|
| 98 |
+
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
|
| 99 |
+
def test_quantize_activations_cpu_matches_independent_reference(dtype: torch.dtype) -> None:
|
| 100 |
+
if not supports_cpu_activation():
|
| 101 |
+
pytest.skip("the built variant has no native CPU activation pipeline")
|
| 102 |
+
torch.manual_seed(41)
|
| 103 |
+
dim = 24
|
| 104 |
+
block_size = 8
|
| 105 |
+
x = torch.randn(2, 3, dim, dtype=dtype)
|
| 106 |
+
x[0, 0].zero_()
|
| 107 |
+
permutation = torch.randperm(dim)
|
| 108 |
+
signs = torch.randint(0, 2, (dim,), dtype=torch.int8).mul(2).sub(1)
|
| 109 |
+
centroids = torch.tanh(torch.linspace(-1.7, 1.7, 16))
|
| 110 |
+
boundaries = (centroids[:-1] + centroids[1:]) / 2
|
| 111 |
+
eps = 1e-10
|
| 112 |
+
|
| 113 |
+
work = x.float()
|
| 114 |
+
norms = work.norm(dim=-1, keepdim=True)
|
| 115 |
+
unit = work / (norms + eps)
|
| 116 |
+
gathered = unit.index_select(-1, permutation) * signs.float()
|
| 117 |
+
rotated = _fwht_reference(gathered.reshape(2, 3, 3, block_size)) / block_size**0.5
|
| 118 |
+
rotated = rotated.reshape_as(work)
|
| 119 |
+
indices = (rotated.unsqueeze(-1) - centroids).abs().argmin(dim=-1)
|
| 120 |
+
expected = (centroids[indices] * norms).to(dtype)
|
| 121 |
+
|
| 122 |
+
actual = quantize_activations_cpu(
|
| 123 |
+
x,
|
| 124 |
+
permutation,
|
| 125 |
+
signs,
|
| 126 |
+
centroids,
|
| 127 |
+
boundaries,
|
| 128 |
+
eps=eps,
|
| 129 |
+
inv_sqrt_block=block_size**-0.5,
|
| 130 |
+
block_size=block_size,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
torch.testing.assert_close(actual, expected, atol=2e-3, rtol=2e-3)
|
| 134 |
+
assert torch.equal(actual[0, 0], torch.zeros(dim, dtype=dtype))
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
@pytest.mark.kernels_ci
|
| 138 |
+
def test_cpu_runtime_isa_dispatch_matches_scalar_reference(monkeypatch) -> None:
|
| 139 |
+
if not supports_device("cpu") or not supports_cpu_activation():
|
| 140 |
+
pytest.skip("the built variant has no complete native CPU pipeline")
|
| 141 |
+
torch.manual_seed(43)
|
| 142 |
+
rows = 8
|
| 143 |
+
in_features = 64
|
| 144 |
+
out_features = 11
|
| 145 |
+
x = torch.randn(rows, in_features)
|
| 146 |
+
indices = torch.randint(0, 16, (out_features, in_features), dtype=torch.uint8)
|
| 147 |
+
packed = _pack(indices, 4)
|
| 148 |
+
row_norms = torch.linspace(0.5, 1.5, out_features)
|
| 149 |
+
centroids = torch.tanh(torch.linspace(-1.7, 1.7, 16))
|
| 150 |
+
boundaries = (centroids[:-1] + centroids[1:]) / 2
|
| 151 |
+
bias = torch.randn(out_features)
|
| 152 |
+
permutation = torch.randperm(in_features)
|
| 153 |
+
signs = torch.randint(0, 2, (in_features,), dtype=torch.int8).mul(2).sub(1)
|
| 154 |
+
|
| 155 |
+
outputs = {}
|
| 156 |
+
activations = {}
|
| 157 |
+
for isa in _runnable_cpu_isas():
|
| 158 |
+
monkeypatch.setenv("ORBITQUANT_CPU_ISA", isa)
|
| 159 |
+
activations[isa] = quantize_activations_cpu(
|
| 160 |
+
x,
|
| 161 |
+
permutation,
|
| 162 |
+
signs,
|
| 163 |
+
centroids,
|
| 164 |
+
boundaries,
|
| 165 |
+
eps=1e-10,
|
| 166 |
+
inv_sqrt_block=in_features**-0.5,
|
| 167 |
+
block_size=in_features,
|
| 168 |
+
)
|
| 169 |
+
outputs[isa] = matmul_packed_weight(
|
| 170 |
+
activations[isa],
|
| 171 |
+
packed,
|
| 172 |
+
row_norms,
|
| 173 |
+
centroids,
|
| 174 |
+
bits=4,
|
| 175 |
+
out_features=out_features,
|
| 176 |
+
in_features=in_features,
|
| 177 |
+
bias=bias,
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
for isa in _runnable_cpu_isas()[1:]:
|
| 181 |
+
torch.testing.assert_close(activations[isa], activations["scalar"], atol=2e-6, rtol=2e-6)
|
| 182 |
+
torch.testing.assert_close(outputs[isa], outputs["scalar"], atol=2e-5, rtol=2e-5)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
@pytest.mark.kernels_ci
|
| 186 |
+
@pytest.mark.parametrize("bits", [2, 3, 6])
|
| 187 |
+
@pytest.mark.parametrize("in_features", [64, 84])
|
| 188 |
+
def test_cpu_isa_matmul_matches_scalar_for_low_bit_widths(
|
| 189 |
+
monkeypatch, bits: int, in_features: int
|
| 190 |
+
) -> None:
|
| 191 |
+
if not supports_device("cpu"):
|
| 192 |
+
pytest.skip("the built variant has no native CPU backend")
|
| 193 |
+
torch.manual_seed(47 + bits + in_features)
|
| 194 |
+
rows = 9
|
| 195 |
+
out_features = 11
|
| 196 |
+
levels = 2**bits
|
| 197 |
+
x = torch.randn(rows, in_features)
|
| 198 |
+
indices = torch.randint(0, levels, (out_features, in_features), dtype=torch.uint8)
|
| 199 |
+
packed = _pack(indices, bits)
|
| 200 |
+
row_norms = torch.linspace(0.5, 1.5, out_features)
|
| 201 |
+
centroids = torch.tanh(torch.linspace(-1.7, 1.7, levels))
|
| 202 |
+
bias = torch.randn(out_features)
|
| 203 |
+
|
| 204 |
+
outputs = {}
|
| 205 |
+
for isa in _runnable_cpu_isas():
|
| 206 |
+
monkeypatch.setenv("ORBITQUANT_CPU_ISA", isa)
|
| 207 |
+
outputs[isa] = matmul_packed_weight(
|
| 208 |
+
x,
|
| 209 |
+
packed,
|
| 210 |
+
row_norms,
|
| 211 |
+
centroids,
|
| 212 |
+
bits=bits,
|
| 213 |
+
out_features=out_features,
|
| 214 |
+
in_features=in_features,
|
| 215 |
+
bias=bias,
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
for isa in _runnable_cpu_isas()[1:]:
|
| 219 |
+
torch.testing.assert_close(outputs[isa], outputs["scalar"], atol=2e-5, rtol=2e-5)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
@pytest.mark.kernels_ci
|
| 223 |
+
@pytest.mark.parametrize("rows", [16, 24, 32, 33])
|
| 224 |
+
def test_cpu_avx2_bf16_realistic_row_tiles_match_reference(monkeypatch, rows: int) -> None:
|
| 225 |
+
if "avx2" not in _runnable_cpu_isas() or not supports_device("cpu"):
|
| 226 |
+
pytest.skip("the built variant has no runnable AVX2 CPU path")
|
| 227 |
+
monkeypatch.setenv("ORBITQUANT_CPU_ISA", "avx2")
|
| 228 |
+
torch.manual_seed(59 + rows)
|
| 229 |
+
in_features = 1536
|
| 230 |
+
out_features = 17
|
| 231 |
+
x = torch.randn(rows, in_features, dtype=torch.bfloat16)
|
| 232 |
+
indices = torch.randint(0, 16, (out_features, in_features), dtype=torch.uint8)
|
| 233 |
+
packed = _pack(indices, 4)
|
| 234 |
+
row_norms = torch.linspace(0.5, 1.5, out_features)
|
| 235 |
+
centroids = torch.tanh(torch.linspace(-1.7, 1.7, 16))
|
| 236 |
+
|
| 237 |
+
expected_weight = (row_norms[:, None] * centroids[indices.long()]).to(torch.bfloat16)
|
| 238 |
+
expected = torch.nn.functional.linear(x, expected_weight)
|
| 239 |
+
actual = matmul_packed_weight(
|
| 240 |
+
x,
|
| 241 |
+
packed,
|
| 242 |
+
row_norms,
|
| 243 |
+
centroids,
|
| 244 |
+
bits=4,
|
| 245 |
+
out_features=out_features,
|
| 246 |
+
in_features=in_features,
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
torch.testing.assert_close(actual, expected, atol=0.25, rtol=3e-2)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
@pytest.mark.kernels_ci
|
| 253 |
+
def test_cpu_avx512_bf16_realistic_row_tile_matches_reference(monkeypatch) -> None:
|
| 254 |
+
if "avx512" not in _runnable_cpu_isas() or not supports_device("cpu"):
|
| 255 |
+
pytest.skip("the built variant has no runnable AVX-512 CPU path")
|
| 256 |
+
monkeypatch.setenv("ORBITQUANT_CPU_ISA", "avx512")
|
| 257 |
+
torch.manual_seed(59)
|
| 258 |
+
rows = 32
|
| 259 |
+
in_features = 1536
|
| 260 |
+
out_features = 17
|
| 261 |
+
x = torch.randn(rows, in_features, dtype=torch.bfloat16)
|
| 262 |
+
indices = torch.randint(0, 16, (out_features, in_features), dtype=torch.uint8)
|
| 263 |
+
packed = _pack(indices, 4)
|
| 264 |
+
row_norms = torch.linspace(0.5, 1.5, out_features)
|
| 265 |
+
centroids = torch.tanh(torch.linspace(-1.7, 1.7, 16))
|
| 266 |
+
|
| 267 |
+
expected_weight = (row_norms[:, None] * centroids[indices.long()]).to(torch.bfloat16)
|
| 268 |
+
expected = torch.nn.functional.linear(x, expected_weight)
|
| 269 |
+
actual = matmul_packed_weight(
|
| 270 |
+
x,
|
| 271 |
+
packed,
|
| 272 |
+
row_norms,
|
| 273 |
+
centroids,
|
| 274 |
+
bits=4,
|
| 275 |
+
out_features=out_features,
|
| 276 |
+
in_features=in_features,
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
torch.testing.assert_close(actual, expected, atol=0.125, rtol=3e-2)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
@pytest.mark.kernels_ci
|
| 283 |
+
@pytest.mark.parametrize("group_size", [8, 64])
|
| 284 |
+
@pytest.mark.parametrize("with_bias", [False, True])
|
| 285 |
+
def test_matmul_packed_adaln_cpu_matches_independent_bf16_reference(
|
| 286 |
+
group_size: int,
|
| 287 |
+
with_bias: bool,
|
| 288 |
+
) -> None:
|
| 289 |
+
if not supports_cpu_adaln():
|
| 290 |
+
pytest.skip("the built variant has no native CPU AdaLN kernel")
|
| 291 |
+
torch.manual_seed(47)
|
| 292 |
+
in_features = 65
|
| 293 |
+
out_features = 9
|
| 294 |
+
num_groups = (in_features + group_size - 1) // group_size
|
| 295 |
+
padded_in_features = num_groups * group_size
|
| 296 |
+
indices = torch.randint(
|
| 297 |
+
0,
|
| 298 |
+
16,
|
| 299 |
+
(out_features, num_groups, group_size),
|
| 300 |
+
dtype=torch.uint8,
|
| 301 |
+
)
|
| 302 |
+
indices.reshape(out_features, padded_in_features)[:, in_features:] = 8
|
| 303 |
+
packed = _pack(indices, 4)
|
| 304 |
+
scales = torch.rand(out_features, num_groups, dtype=torch.bfloat16).mul(0.1)
|
| 305 |
+
x = torch.randn(2, 3, in_features, dtype=torch.bfloat16)
|
| 306 |
+
bias = torch.randn(out_features, dtype=torch.bfloat16) if with_bias else None
|
| 307 |
+
|
| 308 |
+
signed = indices.to(torch.int16).sub(8).float()
|
| 309 |
+
weight = (signed * scales.float()[..., None]).reshape(out_features, padded_in_features)[
|
| 310 |
+
:, :in_features
|
| 311 |
+
]
|
| 312 |
+
expected = torch.nn.functional.linear(x, weight.to(torch.bfloat16), bias)
|
| 313 |
+
actual = matmul_packed_adaln_int4_cpu(
|
| 314 |
+
x,
|
| 315 |
+
packed,
|
| 316 |
+
scales,
|
| 317 |
+
out_features=out_features,
|
| 318 |
+
in_features=in_features,
|
| 319 |
+
group_size=group_size,
|
| 320 |
+
bias=bias,
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
assert actual.dtype == torch.bfloat16
|
| 324 |
+
assert actual.shape == (2, 3, out_features)
|
| 325 |
+
torch.testing.assert_close(actual, expected, atol=2e-2, rtol=2e-2)
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
@pytest.mark.kernels_ci
|
| 329 |
+
def test_cpu_adaln_runtime_isa_dispatch_matches_scalar_reference(monkeypatch) -> None:
|
| 330 |
+
if not supports_cpu_adaln():
|
| 331 |
+
pytest.skip("the built variant has no native CPU AdaLN kernel")
|
| 332 |
+
torch.manual_seed(53)
|
| 333 |
+
rows = 8
|
| 334 |
+
in_features = 64
|
| 335 |
+
out_features = 11
|
| 336 |
+
group_size = 64
|
| 337 |
+
indices = torch.randint(
|
| 338 |
+
0,
|
| 339 |
+
16,
|
| 340 |
+
(out_features, 1, group_size),
|
| 341 |
+
dtype=torch.uint8,
|
| 342 |
+
)
|
| 343 |
+
packed = _pack(indices, 4)
|
| 344 |
+
scales = torch.rand(out_features, 1, dtype=torch.bfloat16).mul(0.1)
|
| 345 |
+
x = torch.randn(rows, in_features, dtype=torch.bfloat16)
|
| 346 |
+
bias = torch.randn(out_features, dtype=torch.bfloat16)
|
| 347 |
+
|
| 348 |
+
outputs = {}
|
| 349 |
+
for isa in _runnable_cpu_isas():
|
| 350 |
+
monkeypatch.setenv("ORBITQUANT_CPU_ISA", isa)
|
| 351 |
+
outputs[isa] = matmul_packed_adaln_int4_cpu(
|
| 352 |
+
x,
|
| 353 |
+
packed,
|
| 354 |
+
scales,
|
| 355 |
+
out_features=out_features,
|
| 356 |
+
in_features=in_features,
|
| 357 |
+
group_size=group_size,
|
| 358 |
+
bias=bias,
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
+
for isa in _runnable_cpu_isas()[1:]:
|
| 362 |
+
torch.testing.assert_close(outputs[isa], outputs["scalar"], atol=3e-2, rtol=3e-2)
|
| 363 |
+
|
| 364 |
+
|
| 365 |
@pytest.mark.kernels_ci
|
| 366 |
@pytest.mark.parametrize("bits", [2, 3, 4, 6])
|
| 367 |
@pytest.mark.parametrize("in_features", [16, 19])
|
|
|
|
| 374 |
rows = 9
|
| 375 |
out_features = 7
|
| 376 |
x = torch.randn(rows, in_features, device=device, dtype=dtype)
|
| 377 |
+
indices = torch.arange(out_features * in_features, dtype=torch.uint8).reshape(
|
| 378 |
+
out_features, in_features
|
| 379 |
+
) % (2**bits)
|
|
|
|
|
|
|
|
|
|
| 380 |
packed = _pack(indices, bits).to(device)
|
| 381 |
+
row_norms = _row_norms(device, out_features)
|
| 382 |
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 383 |
bias = torch.randn(out_features, device=device, dtype=dtype) if with_bias else None
|
| 384 |
|
|
|
|
| 405 |
assert torch.allclose(actual.float().cpu(), expected, atol=2e-2, rtol=2e-2)
|
| 406 |
|
| 407 |
|
| 408 |
+
@pytest.mark.kernels_ci
|
| 409 |
+
@pytest.mark.parametrize("bits", [2, 3, 4, 6])
|
| 410 |
+
@pytest.mark.parametrize("rows", [1, 2, 3, 8, 9, 15])
|
| 411 |
+
@pytest.mark.parametrize(
|
| 412 |
+
("in_features", "out_features"),
|
| 413 |
+
[(32, 32), (37, 29)],
|
| 414 |
+
)
|
| 415 |
+
def test_matmul_packed_weight_short_sequence_matches_reference(
|
| 416 |
+
bits: int,
|
| 417 |
+
rows: int,
|
| 418 |
+
in_features: int,
|
| 419 |
+
out_features: int,
|
| 420 |
+
) -> None:
|
| 421 |
+
torch.manual_seed(1000 + bits * 100 + rows * 10 + in_features + out_features)
|
| 422 |
+
device = _device()
|
| 423 |
+
dtype = torch.float16 if device == "mps" else torch.bfloat16
|
| 424 |
+
x = torch.randn(rows, in_features, device=device, dtype=dtype)
|
| 425 |
+
indices = torch.randint(0, 2**bits, (out_features, in_features), dtype=torch.uint8)
|
| 426 |
+
packed = _pack(indices, bits).to(device)
|
| 427 |
+
row_norms = _row_norms(device, out_features)
|
| 428 |
+
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 429 |
+
bias = torch.randn(out_features, device=device, dtype=dtype)
|
| 430 |
+
|
| 431 |
+
expected_weight = row_norms.cpu()[:, None] * centroids.cpu()[indices.long()]
|
| 432 |
+
if device == "cpu":
|
| 433 |
+
expected = torch.nn.functional.linear(
|
| 434 |
+
x.cpu(),
|
| 435 |
+
expected_weight.to(torch.bfloat16),
|
| 436 |
+
bias.cpu(),
|
| 437 |
+
).float()
|
| 438 |
+
else:
|
| 439 |
+
expected = torch.nn.functional.linear(x.float().cpu(), expected_weight, bias.float().cpu())
|
| 440 |
+
actual = matmul_packed_weight(
|
| 441 |
+
x,
|
| 442 |
+
packed,
|
| 443 |
+
row_norms,
|
| 444 |
+
centroids,
|
| 445 |
+
bits=bits,
|
| 446 |
+
out_features=out_features,
|
| 447 |
+
in_features=in_features,
|
| 448 |
+
bias=bias,
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
assert actual.shape == (rows, out_features)
|
| 452 |
+
assert torch.allclose(actual.float().cpu(), expected, atol=3e-2, rtol=3e-2)
|
| 453 |
+
|
| 454 |
+
|
| 455 |
@pytest.mark.kernels_ci
|
| 456 |
def test_matmul_packed_weight_explicit_mps_path_matches_dequantized_reference() -> None:
|
| 457 |
device = _mps_device()
|
|
|
|
| 460 |
in_features = 19
|
| 461 |
out_features = 7
|
| 462 |
x = torch.randn(rows, in_features, device=device, dtype=torch.float16)
|
| 463 |
+
indices = torch.arange(out_features * in_features, dtype=torch.uint8).reshape(
|
| 464 |
+
out_features, in_features
|
| 465 |
+
) % (2**bits)
|
|
|
|
|
|
|
|
|
|
| 466 |
packed = _pack(indices, bits).to(device)
|
| 467 |
+
row_norms = _row_norms(device, out_features)
|
| 468 |
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 469 |
bias = torch.randn(out_features, device=device, dtype=torch.float16)
|
| 470 |
|
|
|
|
| 498 |
in_features = 19
|
| 499 |
out_features = 7
|
| 500 |
x = torch.randn(rows, in_features, device=device, dtype=torch.bfloat16)
|
| 501 |
+
indices = torch.arange(out_features * in_features, dtype=torch.uint8).reshape(
|
| 502 |
+
out_features, in_features
|
| 503 |
+
) % (2**bits)
|
|
|
|
|
|
|
|
|
|
| 504 |
packed = _pack(indices, bits).to(device)
|
| 505 |
+
row_norms = _row_norms(device, out_features)
|
| 506 |
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 507 |
bias = torch.randn(out_features, device=device, dtype=torch.bfloat16)
|
| 508 |
|
|
|
|
| 526 |
assert actual.dtype == torch.bfloat16
|
| 527 |
assert actual.shape == (rows, out_features)
|
| 528 |
assert torch.allclose(actual.float().cpu(), expected, atol=3e-2, rtol=3e-2)
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
@pytest.mark.kernels_ci
|
| 532 |
+
@pytest.mark.parametrize("bits", [2, 3, 4, 6])
|
| 533 |
+
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16])
|
| 534 |
+
def test_matmul_packed_weight_mps_aligned_mma_path_matches_dequantized_reference(
|
| 535 |
+
bits: int,
|
| 536 |
+
dtype: torch.dtype,
|
| 537 |
+
) -> None:
|
| 538 |
+
device = _mps_bfloat16_device() if dtype == torch.bfloat16 else _mps_device()
|
| 539 |
+
rows = in_features = out_features = 32
|
| 540 |
+
x = torch.randn(rows, in_features, device=device, dtype=dtype)
|
| 541 |
+
indices = torch.arange(out_features * in_features, dtype=torch.uint8).reshape(
|
| 542 |
+
out_features, in_features
|
| 543 |
+
) % (2**bits)
|
| 544 |
+
packed = _pack(indices, bits).to(device)
|
| 545 |
+
row_norms = _row_norms(device, out_features)
|
| 546 |
+
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 547 |
+
bias = torch.randn(out_features, device=device, dtype=dtype)
|
| 548 |
+
|
| 549 |
+
expected_weight = row_norms.cpu()[:, None] * centroids.cpu()[indices.long()]
|
| 550 |
+
expected = torch.nn.functional.linear(x.float().cpu(), expected_weight, bias.float().cpu())
|
| 551 |
+
actual = matmul_packed_weight(
|
| 552 |
+
x,
|
| 553 |
+
packed,
|
| 554 |
+
row_norms,
|
| 555 |
+
centroids,
|
| 556 |
+
bits=bits,
|
| 557 |
+
out_features=out_features,
|
| 558 |
+
in_features=in_features,
|
| 559 |
+
bias=bias,
|
| 560 |
+
)
|
| 561 |
+
|
| 562 |
+
tolerance = 3e-2 if dtype == torch.bfloat16 else 2e-2
|
| 563 |
+
assert actual.dtype == dtype
|
| 564 |
+
assert torch.allclose(actual.float().cpu(), expected, atol=tolerance, rtol=tolerance)
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
@pytest.mark.kernels_ci
|
| 568 |
+
@pytest.mark.parametrize("bits", [2, 3, 4, 6])
|
| 569 |
+
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16])
|
| 570 |
+
def test_matmul_packed_weight_cuda_mma64_path_matches_dequantized_reference(
|
| 571 |
+
bits: int,
|
| 572 |
+
dtype: torch.dtype,
|
| 573 |
+
) -> None:
|
| 574 |
+
device = _cuda_device()
|
| 575 |
+
rows = 65
|
| 576 |
+
in_features = 64
|
| 577 |
+
out_features = 70
|
| 578 |
+
x = torch.randn(rows, in_features, device=device, dtype=dtype)
|
| 579 |
+
indices = torch.arange(out_features * in_features, dtype=torch.uint8).reshape(
|
| 580 |
+
out_features, in_features
|
| 581 |
+
) % (2**bits)
|
| 582 |
+
packed = _pack(indices, bits).to(device)
|
| 583 |
+
row_norms = _row_norms(device, out_features)
|
| 584 |
+
centroids = torch.linspace(-1.0, 1.0, 2**bits, device=device)
|
| 585 |
+
bias = torch.randn(out_features, device=device, dtype=dtype)
|
| 586 |
+
|
| 587 |
+
expected_weight = (row_norms[:, None] * centroids[indices.long().to(device)]).to(dtype)
|
| 588 |
+
expected = torch.nn.functional.linear(x, expected_weight, bias)
|
| 589 |
+
actual = matmul_packed_weight(
|
| 590 |
+
x,
|
| 591 |
+
packed,
|
| 592 |
+
row_norms,
|
| 593 |
+
centroids,
|
| 594 |
+
bits=bits,
|
| 595 |
+
out_features=out_features,
|
| 596 |
+
in_features=in_features,
|
| 597 |
+
bias=bias,
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
tolerance = 3e-2 if dtype == torch.bfloat16 else 2e-2
|
| 601 |
+
assert actual.dtype == dtype
|
| 602 |
+
assert torch.allclose(actual.float(), expected.float(), atol=tolerance, rtol=tolerance)
|
| 603 |
+
|
| 604 |
+
|
| 605 |
+
@pytest.mark.kernels_ci
|
| 606 |
+
@pytest.mark.parametrize(
|
| 607 |
+
("rows", "out_features", "tile_m", "tile_n"),
|
| 608 |
+
[
|
| 609 |
+
(128, 128, 128, 128),
|
| 610 |
+
(256, 128, 256, 128),
|
| 611 |
+
(128, 256, 128, 256),
|
| 612 |
+
(130, 258, 128, 128),
|
| 613 |
+
],
|
| 614 |
+
)
|
| 615 |
+
@pytest.mark.parametrize("weight_k_major", [False, True])
|
| 616 |
+
def test_matmul_packed_w4a4_async_matches_sync_and_float_reference(
|
| 617 |
+
rows: int,
|
| 618 |
+
out_features: int,
|
| 619 |
+
tile_m: int,
|
| 620 |
+
tile_n: int,
|
| 621 |
+
weight_k_major: bool,
|
| 622 |
+
) -> None:
|
| 623 |
+
device = _cuda_device()
|
| 624 |
+
in_features = 256
|
| 625 |
+
torch.manual_seed(0)
|
| 626 |
+
activation_indices = torch.randint(0, 16, (rows, in_features), device=device, dtype=torch.uint8)
|
| 627 |
+
weight_indices = torch.randint(
|
| 628 |
+
0, 16, (out_features, in_features), device=device, dtype=torch.uint8
|
| 629 |
+
)
|
| 630 |
+
packed_activations = (
|
| 631 |
+
activation_indices[:, 0::2] | (activation_indices[:, 1::2] << 4)
|
| 632 |
+
).contiguous()
|
| 633 |
+
row_major_weights = (weight_indices[:, 0::2] | (weight_indices[:, 1::2] << 4)).contiguous()
|
| 634 |
+
packed_weights = row_major_weights.T.contiguous() if weight_k_major else row_major_weights
|
| 635 |
+
codes = torch.tensor(
|
| 636 |
+
[-104, -79, -62, -48, -36, -25, -15, -5, 5, 15, 25, 36, 48, 62, 79, 104],
|
| 637 |
+
device=device,
|
| 638 |
+
dtype=torch.int8,
|
| 639 |
+
)
|
| 640 |
+
token_norms = torch.linspace(0.5, 1.0, rows, device=device)
|
| 641 |
+
row_norms = torch.linspace(0.5, 1.5, out_features, device=device, dtype=torch.bfloat16)
|
| 642 |
+
activation_scale = 0.005
|
| 643 |
+
weight_scale = 0.005
|
| 644 |
+
|
| 645 |
+
kwargs = {
|
| 646 |
+
"activation_scale": activation_scale,
|
| 647 |
+
"weight_scale": weight_scale,
|
| 648 |
+
"out_features": out_features,
|
| 649 |
+
"in_features": in_features,
|
| 650 |
+
"tile_m": tile_m,
|
| 651 |
+
"tile_n": tile_n,
|
| 652 |
+
}
|
| 653 |
+
sync = matmul_packed_w4a4_int8(
|
| 654 |
+
packed_activations,
|
| 655 |
+
packed_weights,
|
| 656 |
+
token_norms,
|
| 657 |
+
row_norms,
|
| 658 |
+
codes,
|
| 659 |
+
codes,
|
| 660 |
+
async_packed=False,
|
| 661 |
+
weight_k_major=weight_k_major,
|
| 662 |
+
**kwargs,
|
| 663 |
+
)
|
| 664 |
+
asynchronous = matmul_packed_w4a4_int8(
|
| 665 |
+
packed_activations,
|
| 666 |
+
packed_weights,
|
| 667 |
+
token_norms,
|
| 668 |
+
row_norms,
|
| 669 |
+
codes,
|
| 670 |
+
codes,
|
| 671 |
+
async_packed=True,
|
| 672 |
+
weight_k_major=weight_k_major,
|
| 673 |
+
**kwargs,
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
assert torch.equal(asynchronous, sync)
|
| 677 |
+
|
| 678 |
+
activation_values = codes[activation_indices.long()].float()
|
| 679 |
+
weight_values = codes[weight_indices.long()].float()
|
| 680 |
+
reference = activation_values @ weight_values.T
|
| 681 |
+
reference *= token_norms[:, None]
|
| 682 |
+
reference *= row_norms.float()[None, :]
|
| 683 |
+
reference *= activation_scale * weight_scale
|
| 684 |
+
|
| 685 |
+
assert torch.allclose(asynchronous.float(), reference, atol=0.25, rtol=1e-2)
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
@pytest.mark.kernels_ci
|
| 689 |
+
@pytest.mark.parametrize(("dim", "threads"), [(512, 128), (4096, 256), (16384, 512)])
|
| 690 |
+
def test_quantize_activations_packed_w4_matches_torch_reference(
|
| 691 |
+
dim: int,
|
| 692 |
+
threads: int,
|
| 693 |
+
) -> None:
|
| 694 |
+
device = _cuda_device()
|
| 695 |
+
rows = 2
|
| 696 |
+
x = torch.zeros((rows, dim), device=device, dtype=torch.bfloat16)
|
| 697 |
+
x[0, 3] = 1
|
| 698 |
+
x[1, dim - 7] = -1
|
| 699 |
+
permutation = torch.randperm(dim, device=device)
|
| 700 |
+
signs = torch.where(
|
| 701 |
+
torch.arange(dim, device=device) % 2 == 0,
|
| 702 |
+
torch.ones(dim, device=device, dtype=torch.int8),
|
| 703 |
+
-torch.ones(dim, device=device, dtype=torch.int8),
|
| 704 |
+
)
|
| 705 |
+
boundaries = torch.linspace(-0.2, 0.2, 15, device=device)
|
| 706 |
+
eps = 1e-12
|
| 707 |
+
inv_sqrt_block = dim**-0.5
|
| 708 |
+
|
| 709 |
+
packed, norms = quantize_activations_packed_w4(
|
| 710 |
+
x,
|
| 711 |
+
permutation,
|
| 712 |
+
signs,
|
| 713 |
+
boundaries,
|
| 714 |
+
eps=eps,
|
| 715 |
+
inv_sqrt_block=inv_sqrt_block,
|
| 716 |
+
threads=threads,
|
| 717 |
+
)
|
| 718 |
+
|
| 719 |
+
work = x.float()[:, permutation] * signs.float()
|
| 720 |
+
expected_norms = work.norm(dim=-1)
|
| 721 |
+
work /= expected_norms[:, None] + eps
|
| 722 |
+
width = 1
|
| 723 |
+
while width < dim:
|
| 724 |
+
blocks = work.reshape(rows, -1, width * 2)
|
| 725 |
+
left = blocks[..., :width]
|
| 726 |
+
right = blocks[..., width:]
|
| 727 |
+
work = torch.cat((left + right, left - right), dim=-1).reshape(rows, dim)
|
| 728 |
+
width *= 2
|
| 729 |
+
indices = torch.bucketize(work * inv_sqrt_block, boundaries).to(torch.uint8)
|
| 730 |
+
expected_packed = (indices[:, 0::2] | (indices[:, 1::2] << 4)).contiguous()
|
| 731 |
+
|
| 732 |
+
assert torch.equal(packed, expected_packed)
|
| 733 |
+
assert torch.allclose(norms, expected_norms, atol=1e-6, rtol=1e-6)
|
| 734 |
+
|
| 735 |
+
|
| 736 |
+
@pytest.mark.kernels_ci
|
| 737 |
+
def test_quantize_activations_packed_w4_accepts_int32_permutation() -> None:
|
| 738 |
+
device = _cuda_device()
|
| 739 |
+
dim = 512
|
| 740 |
+
torch.manual_seed(0)
|
| 741 |
+
x = torch.randn((3, dim), device=device, dtype=torch.bfloat16)
|
| 742 |
+
permutation = torch.randperm(dim, device=device)
|
| 743 |
+
signs = torch.where(
|
| 744 |
+
torch.arange(dim, device=device) % 2 == 0,
|
| 745 |
+
torch.ones(dim, device=device, dtype=torch.int8),
|
| 746 |
+
-torch.ones(dim, device=device, dtype=torch.int8),
|
| 747 |
+
)
|
| 748 |
+
boundaries = torch.linspace(-0.2, 0.2, 15, device=device)
|
| 749 |
+
|
| 750 |
+
packed_int64, norms_int64 = quantize_activations_packed_w4(
|
| 751 |
+
x,
|
| 752 |
+
permutation,
|
| 753 |
+
signs,
|
| 754 |
+
boundaries,
|
| 755 |
+
eps=1e-12,
|
| 756 |
+
inv_sqrt_block=dim**-0.5,
|
| 757 |
+
threads=256,
|
| 758 |
+
)
|
| 759 |
+
packed_int32, norms_int32 = quantize_activations_packed_w4(
|
| 760 |
+
x,
|
| 761 |
+
permutation.to(torch.int32),
|
| 762 |
+
signs,
|
| 763 |
+
boundaries,
|
| 764 |
+
eps=1e-12,
|
| 765 |
+
inv_sqrt_block=dim**-0.5,
|
| 766 |
+
threads=256,
|
| 767 |
+
)
|
| 768 |
+
|
| 769 |
+
assert torch.equal(packed_int32, packed_int64)
|
| 770 |
+
assert torch.equal(norms_int32, norms_int64)
|
| 771 |
+
|
| 772 |
+
|
| 773 |
+
@pytest.mark.kernels_ci
|
| 774 |
+
@pytest.mark.parametrize(("dim", "threads"), [(512, 128), (4096, 256), (16384, 512)])
|
| 775 |
+
def test_quantize_activations_int8_matches_packed_codes(
|
| 776 |
+
dim: int,
|
| 777 |
+
threads: int,
|
| 778 |
+
) -> None:
|
| 779 |
+
device = _cuda_device()
|
| 780 |
+
x = torch.randn((2, dim), device=device, dtype=torch.bfloat16)
|
| 781 |
+
permutation = torch.randperm(dim, device=device)
|
| 782 |
+
signs = torch.where(
|
| 783 |
+
torch.arange(dim, device=device) % 2 == 0,
|
| 784 |
+
torch.ones(dim, device=device, dtype=torch.int8),
|
| 785 |
+
-torch.ones(dim, device=device, dtype=torch.int8),
|
| 786 |
+
)
|
| 787 |
+
boundaries = torch.linspace(-0.2, 0.2, 15, device=device)
|
| 788 |
+
codes = torch.tensor(
|
| 789 |
+
[-120, -92, -68, -49, -34, -22, -12, -4, 4, 12, 22, 34, 49, 68, 92, 120],
|
| 790 |
+
device=device,
|
| 791 |
+
dtype=torch.int8,
|
| 792 |
+
)
|
| 793 |
+
kwargs = {
|
| 794 |
+
"eps": 1e-12,
|
| 795 |
+
"inv_sqrt_block": dim**-0.5,
|
| 796 |
+
"threads": threads,
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
packed, packed_norms = quantize_activations_packed_w4(
|
| 800 |
+
x,
|
| 801 |
+
permutation,
|
| 802 |
+
signs,
|
| 803 |
+
boundaries,
|
| 804 |
+
**kwargs,
|
| 805 |
+
)
|
| 806 |
+
quantized, int8_norms = quantize_activations_int8(
|
| 807 |
+
x,
|
| 808 |
+
permutation,
|
| 809 |
+
signs,
|
| 810 |
+
boundaries,
|
| 811 |
+
codes,
|
| 812 |
+
**kwargs,
|
| 813 |
+
)
|
| 814 |
+
|
| 815 |
+
indices = torch.empty((2, dim), device=device, dtype=torch.long)
|
| 816 |
+
indices[:, 0::2] = packed & 15
|
| 817 |
+
indices[:, 1::2] = packed >> 4
|
| 818 |
+
expected = codes[indices]
|
| 819 |
+
|
| 820 |
+
assert torch.equal(quantized, expected)
|
| 821 |
+
assert torch.equal(int8_norms, packed_norms)
|
| 822 |
+
|
| 823 |
+
|
| 824 |
+
@pytest.mark.kernels_ci
|
| 825 |
+
def test_quantize_activations_int8_matches_blocked_rpbh_reference() -> None:
|
| 826 |
+
device = _cuda_device()
|
| 827 |
+
rows = 2
|
| 828 |
+
dim = 12288
|
| 829 |
+
block_size = 4096
|
| 830 |
+
x = torch.randn((rows, dim), device=device, dtype=torch.bfloat16)
|
| 831 |
+
permutation = torch.randperm(dim, device=device)
|
| 832 |
+
signs = torch.where(
|
| 833 |
+
torch.arange(dim, device=device) % 2 == 0,
|
| 834 |
+
torch.ones(dim, device=device, dtype=torch.int8),
|
| 835 |
+
-torch.ones(dim, device=device, dtype=torch.int8),
|
| 836 |
+
)
|
| 837 |
+
boundaries = torch.linspace(-0.2, 0.2, 15, device=device)
|
| 838 |
+
codes = torch.tensor(
|
| 839 |
+
[-120, -92, -68, -49, -34, -22, -12, -4, 4, 12, 22, 34, 49, 68, 92, 120],
|
| 840 |
+
device=device,
|
| 841 |
+
dtype=torch.int8,
|
| 842 |
+
)
|
| 843 |
+
|
| 844 |
+
quantized, norms = quantize_activations_int8(
|
| 845 |
+
x,
|
| 846 |
+
permutation,
|
| 847 |
+
signs,
|
| 848 |
+
boundaries,
|
| 849 |
+
codes,
|
| 850 |
+
eps=1e-12,
|
| 851 |
+
inv_sqrt_block=block_size**-0.5,
|
| 852 |
+
threads=512,
|
| 853 |
+
)
|
| 854 |
+
|
| 855 |
+
work = x.float()[:, permutation] * signs.float()
|
| 856 |
+
expected_norms = work.norm(dim=-1)
|
| 857 |
+
work = (work / expected_norms[:, None]).reshape(rows, -1, block_size)
|
| 858 |
+
width = 1
|
| 859 |
+
while width < block_size:
|
| 860 |
+
blocks = work.reshape(rows, -1, width * 2)
|
| 861 |
+
left = blocks[..., :width]
|
| 862 |
+
right = blocks[..., width:]
|
| 863 |
+
work = torch.cat((left + right, left - right), dim=-1).reshape(rows, -1, block_size)
|
| 864 |
+
width *= 2
|
| 865 |
+
indices = torch.bucketize(work.reshape(rows, dim) * (block_size**-0.5), boundaries)
|
| 866 |
+
expected = codes[indices]
|
| 867 |
+
|
| 868 |
+
assert torch.equal(quantized, expected)
|
| 869 |
+
assert torch.allclose(norms, expected_norms, atol=1e-5, rtol=1e-6)
|
torch-ext/orbitquant_packed_matmul/__init__.py
CHANGED
|
@@ -4,7 +4,106 @@ import torch
|
|
| 4 |
|
| 5 |
from ._ops import ops
|
| 6 |
|
| 7 |
-
__all__ = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
def matmul_packed_weight(
|
|
@@ -17,9 +116,9 @@ def matmul_packed_weight(
|
|
| 17 |
out_features: int,
|
| 18 |
in_features: int,
|
| 19 |
bias: torch.Tensor | None = None,
|
| 20 |
-
block_m: int =
|
| 21 |
block_n: int = 64,
|
| 22 |
-
block_k: int =
|
| 23 |
) -> torch.Tensor:
|
| 24 |
if bits <= 0 or bits > 8:
|
| 25 |
raise ValueError("bits must be in [1, 8]")
|
|
@@ -32,13 +131,15 @@ def matmul_packed_weight(
|
|
| 32 |
x_2d = x.contiguous().reshape(-1, in_features)
|
| 33 |
out = torch.empty((x_2d.shape[0], out_features), device=x.device, dtype=x.dtype)
|
| 34 |
packed = packed_weight_indices.to(device=x.device, dtype=torch.uint8).contiguous()
|
| 35 |
-
|
|
|
|
| 36 |
centroid_values = centroids.to(device=x.device, dtype=torch.float32).contiguous()
|
| 37 |
if bias is None:
|
| 38 |
bias_values = torch.empty((1,), device=x.device, dtype=torch.float32)
|
| 39 |
has_bias = False
|
| 40 |
else:
|
| 41 |
-
|
|
|
|
| 42 |
has_bias = True
|
| 43 |
|
| 44 |
ops.matmul_packed_weight(
|
|
@@ -57,3 +158,234 @@ def matmul_packed_weight(
|
|
| 57 |
block_k,
|
| 58 |
)
|
| 59 |
return out.reshape(*original_shape[:-1], out_features)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
from ._ops import ops
|
| 6 |
|
| 7 |
+
__all__ = [
|
| 8 |
+
"matmul_packed_w4a4_int8",
|
| 9 |
+
"matmul_packed_adaln_int4_cpu",
|
| 10 |
+
"matmul_packed_weight",
|
| 11 |
+
"quantize_activations_cpu",
|
| 12 |
+
"quantize_activations_int8",
|
| 13 |
+
"quantize_activations_packed_w4",
|
| 14 |
+
"supports_cpu_activation",
|
| 15 |
+
"supports_cpu_adaln",
|
| 16 |
+
"supports_device",
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def supports_device(device_type: str) -> bool:
|
| 21 |
+
dispatch_keys = {
|
| 22 |
+
"cpu": "CPU",
|
| 23 |
+
"cuda": "CUDA",
|
| 24 |
+
"mps": "MPS",
|
| 25 |
+
}
|
| 26 |
+
try:
|
| 27 |
+
dispatch_key = dispatch_keys[device_type]
|
| 28 |
+
except KeyError as exc:
|
| 29 |
+
raise ValueError(f"unknown device type {device_type!r}") from exc
|
| 30 |
+
return bool(
|
| 31 |
+
torch._C._dispatch_has_kernel_for_dispatch_key( # noqa: SLF001
|
| 32 |
+
ops.matmul_packed_weight._qualified_op_name, # noqa: SLF001
|
| 33 |
+
dispatch_key,
|
| 34 |
+
)
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def supports_cpu_activation() -> bool:
|
| 39 |
+
try:
|
| 40 |
+
operation = ops.quantize_activations_cpu
|
| 41 |
+
qualified_name = operation._qualified_op_name # noqa: SLF001
|
| 42 |
+
except (AttributeError, RuntimeError):
|
| 43 |
+
return False
|
| 44 |
+
return bool(
|
| 45 |
+
torch._C._dispatch_has_kernel_for_dispatch_key( # noqa: SLF001
|
| 46 |
+
qualified_name,
|
| 47 |
+
"CPU",
|
| 48 |
+
)
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def supports_cpu_adaln() -> bool:
|
| 53 |
+
try:
|
| 54 |
+
operation = ops.matmul_packed_adaln_int4_cpu
|
| 55 |
+
qualified_name = operation._qualified_op_name # noqa: SLF001
|
| 56 |
+
except (AttributeError, RuntimeError):
|
| 57 |
+
return False
|
| 58 |
+
return bool(
|
| 59 |
+
torch._C._dispatch_has_kernel_for_dispatch_key( # noqa: SLF001
|
| 60 |
+
qualified_name,
|
| 61 |
+
"CPU",
|
| 62 |
+
)
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def quantize_activations_cpu(
|
| 67 |
+
x: torch.Tensor,
|
| 68 |
+
permutation: torch.Tensor,
|
| 69 |
+
signs: torch.Tensor,
|
| 70 |
+
centroids: torch.Tensor,
|
| 71 |
+
boundaries: torch.Tensor,
|
| 72 |
+
*,
|
| 73 |
+
eps: float,
|
| 74 |
+
inv_sqrt_block: float,
|
| 75 |
+
block_size: int,
|
| 76 |
+
) -> torch.Tensor:
|
| 77 |
+
if x.device.type != "cpu":
|
| 78 |
+
raise RuntimeError("native CPU activation quantization requires CPU tensors")
|
| 79 |
+
if x.dtype not in {torch.float32, torch.float16, torch.bfloat16}:
|
| 80 |
+
raise ValueError("x must be float32, float16, or bfloat16")
|
| 81 |
+
if block_size <= 0 or block_size & (block_size - 1):
|
| 82 |
+
raise ValueError("block_size must be a positive power of two")
|
| 83 |
+
dim = x.shape[-1]
|
| 84 |
+
if dim % block_size != 0:
|
| 85 |
+
raise ValueError("block_size must divide the input dimension")
|
| 86 |
+
|
| 87 |
+
original_shape = x.shape
|
| 88 |
+
values = x.contiguous().reshape(-1, dim)
|
| 89 |
+
out = torch.empty_like(values)
|
| 90 |
+
permutation_dtype = torch.int32 if permutation.dtype == torch.int32 else torch.int64
|
| 91 |
+
permutation_values = permutation.to(device="cpu", dtype=permutation_dtype).contiguous()
|
| 92 |
+
sign_values = signs.to(device="cpu", dtype=torch.int8).contiguous()
|
| 93 |
+
centroid_values = centroids.to(device="cpu", dtype=torch.float32).contiguous()
|
| 94 |
+
boundary_values = boundaries.to(device="cpu", dtype=torch.float32).contiguous()
|
| 95 |
+
ops.quantize_activations_cpu(
|
| 96 |
+
out,
|
| 97 |
+
values,
|
| 98 |
+
permutation_values,
|
| 99 |
+
sign_values,
|
| 100 |
+
centroid_values,
|
| 101 |
+
boundary_values,
|
| 102 |
+
eps,
|
| 103 |
+
inv_sqrt_block,
|
| 104 |
+
block_size,
|
| 105 |
+
)
|
| 106 |
+
return out.reshape(original_shape)
|
| 107 |
|
| 108 |
|
| 109 |
def matmul_packed_weight(
|
|
|
|
| 116 |
out_features: int,
|
| 117 |
in_features: int,
|
| 118 |
bias: torch.Tensor | None = None,
|
| 119 |
+
block_m: int = 64,
|
| 120 |
block_n: int = 64,
|
| 121 |
+
block_k: int = 128,
|
| 122 |
) -> torch.Tensor:
|
| 123 |
if bits <= 0 or bits > 8:
|
| 124 |
raise ValueError("bits must be in [1, 8]")
|
|
|
|
| 131 |
x_2d = x.contiguous().reshape(-1, in_features)
|
| 132 |
out = torch.empty((x_2d.shape[0], out_features), device=x.device, dtype=x.dtype)
|
| 133 |
packed = packed_weight_indices.to(device=x.device, dtype=torch.uint8).contiguous()
|
| 134 |
+
auxiliary_dtype = torch.bfloat16 if x.device.type == "cuda" else torch.float32
|
| 135 |
+
norms = row_norms.to(device=x.device, dtype=auxiliary_dtype).contiguous()
|
| 136 |
centroid_values = centroids.to(device=x.device, dtype=torch.float32).contiguous()
|
| 137 |
if bias is None:
|
| 138 |
bias_values = torch.empty((1,), device=x.device, dtype=torch.float32)
|
| 139 |
has_bias = False
|
| 140 |
else:
|
| 141 |
+
bias_dtype = x.dtype if x.device.type == "cuda" else torch.float32
|
| 142 |
+
bias_values = bias.to(device=x.device, dtype=bias_dtype).contiguous()
|
| 143 |
has_bias = True
|
| 144 |
|
| 145 |
ops.matmul_packed_weight(
|
|
|
|
| 158 |
block_k,
|
| 159 |
)
|
| 160 |
return out.reshape(*original_shape[:-1], out_features)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def matmul_packed_adaln_int4_cpu(
|
| 164 |
+
x: torch.Tensor,
|
| 165 |
+
packed_weight: torch.Tensor,
|
| 166 |
+
scales: torch.Tensor,
|
| 167 |
+
*,
|
| 168 |
+
out_features: int,
|
| 169 |
+
in_features: int,
|
| 170 |
+
group_size: int,
|
| 171 |
+
bias: torch.Tensor | None = None,
|
| 172 |
+
) -> torch.Tensor:
|
| 173 |
+
if x.device.type != "cpu":
|
| 174 |
+
raise RuntimeError("native packed AdaLN requires CPU tensors")
|
| 175 |
+
if x.shape[-1] != in_features:
|
| 176 |
+
raise ValueError(f"expected input last dimension {in_features}, got {x.shape[-1]}")
|
| 177 |
+
if group_size <= 0:
|
| 178 |
+
raise ValueError("group_size must be positive")
|
| 179 |
+
|
| 180 |
+
original_shape = x.shape
|
| 181 |
+
x_2d = x.to(dtype=torch.bfloat16).contiguous().reshape(-1, in_features)
|
| 182 |
+
out = torch.empty((x_2d.shape[0], out_features), dtype=torch.bfloat16)
|
| 183 |
+
packed = packed_weight.to(device="cpu", dtype=torch.uint8).contiguous()
|
| 184 |
+
scale_values = scales.to(device="cpu", dtype=torch.float32).contiguous()
|
| 185 |
+
if bias is None:
|
| 186 |
+
bias_values = torch.empty((1,), dtype=torch.float32)
|
| 187 |
+
has_bias = False
|
| 188 |
+
else:
|
| 189 |
+
bias_values = (
|
| 190 |
+
bias.to(device="cpu", dtype=torch.bfloat16).to(dtype=torch.float32).contiguous()
|
| 191 |
+
)
|
| 192 |
+
has_bias = True
|
| 193 |
+
ops.matmul_packed_adaln_int4_cpu(
|
| 194 |
+
out,
|
| 195 |
+
x_2d,
|
| 196 |
+
packed,
|
| 197 |
+
scale_values,
|
| 198 |
+
bias_values,
|
| 199 |
+
has_bias,
|
| 200 |
+
out_features,
|
| 201 |
+
in_features,
|
| 202 |
+
group_size,
|
| 203 |
+
)
|
| 204 |
+
return out.reshape(*original_shape[:-1], out_features)
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def matmul_packed_w4a4_int8(
|
| 208 |
+
packed_activations: torch.Tensor,
|
| 209 |
+
packed_weight_indices: torch.Tensor,
|
| 210 |
+
token_norms: torch.Tensor,
|
| 211 |
+
row_norms: torch.Tensor,
|
| 212 |
+
activation_codes: torch.Tensor,
|
| 213 |
+
weight_codes: torch.Tensor,
|
| 214 |
+
*,
|
| 215 |
+
activation_scale: float,
|
| 216 |
+
weight_scale: float,
|
| 217 |
+
out_features: int,
|
| 218 |
+
in_features: int,
|
| 219 |
+
bias: torch.Tensor | None = None,
|
| 220 |
+
output_dtype: torch.dtype = torch.bfloat16,
|
| 221 |
+
tile_m: int = 128,
|
| 222 |
+
tile_n: int = 128,
|
| 223 |
+
async_packed: bool = False,
|
| 224 |
+
weight_k_major: bool = False,
|
| 225 |
+
) -> torch.Tensor:
|
| 226 |
+
if not packed_activations.is_cuda:
|
| 227 |
+
raise RuntimeError("packed W4A4 INT8 matmul requires CUDA tensors")
|
| 228 |
+
if in_features <= 0 or in_features % 64 != 0:
|
| 229 |
+
raise ValueError("in_features must be positive and divisible by 64")
|
| 230 |
+
if packed_activations.shape[-1] != in_features // 2:
|
| 231 |
+
raise ValueError(
|
| 232 |
+
f"expected packed activation last dimension {in_features // 2}, "
|
| 233 |
+
f"got {packed_activations.shape[-1]}"
|
| 234 |
+
)
|
| 235 |
+
if output_dtype not in {torch.float16, torch.bfloat16}:
|
| 236 |
+
raise ValueError("output_dtype must be float16 or bfloat16")
|
| 237 |
+
if (tile_m, tile_n) not in {(128, 128), (256, 128), (128, 256)}:
|
| 238 |
+
raise ValueError("tile must be 128x128, 256x128, or 128x256")
|
| 239 |
+
|
| 240 |
+
original_shape = packed_activations.shape
|
| 241 |
+
activations = (
|
| 242 |
+
packed_activations.to(dtype=torch.uint8).contiguous().reshape(-1, in_features // 2)
|
| 243 |
+
)
|
| 244 |
+
weights = packed_weight_indices.to(device=activations.device, dtype=torch.uint8).contiguous()
|
| 245 |
+
norms = token_norms.to(device=activations.device, dtype=torch.float32).contiguous()
|
| 246 |
+
weight_norms = row_norms.to(device=activations.device, dtype=torch.bfloat16).contiguous()
|
| 247 |
+
activation_code_values = activation_codes.to(
|
| 248 |
+
device=activations.device, dtype=torch.int8
|
| 249 |
+
).contiguous()
|
| 250 |
+
weight_code_values = weight_codes.to(device=activations.device, dtype=torch.int8).contiguous()
|
| 251 |
+
out = torch.empty(
|
| 252 |
+
(activations.shape[0], out_features),
|
| 253 |
+
device=activations.device,
|
| 254 |
+
dtype=output_dtype,
|
| 255 |
+
)
|
| 256 |
+
if bias is None:
|
| 257 |
+
bias_values = out
|
| 258 |
+
has_bias = False
|
| 259 |
+
else:
|
| 260 |
+
bias_values = bias.to(device=activations.device, dtype=output_dtype).contiguous()
|
| 261 |
+
has_bias = True
|
| 262 |
+
|
| 263 |
+
ops.matmul_packed_w4a4_int8(
|
| 264 |
+
out,
|
| 265 |
+
activations,
|
| 266 |
+
weights,
|
| 267 |
+
norms,
|
| 268 |
+
weight_norms,
|
| 269 |
+
activation_code_values,
|
| 270 |
+
weight_code_values,
|
| 271 |
+
bias_values,
|
| 272 |
+
has_bias,
|
| 273 |
+
activation_scale,
|
| 274 |
+
weight_scale,
|
| 275 |
+
out_features,
|
| 276 |
+
in_features,
|
| 277 |
+
tile_m,
|
| 278 |
+
tile_n,
|
| 279 |
+
async_packed,
|
| 280 |
+
weight_k_major,
|
| 281 |
+
)
|
| 282 |
+
return out.reshape(*original_shape[:-1], out_features)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def quantize_activations_packed_w4(
|
| 286 |
+
x: torch.Tensor,
|
| 287 |
+
permutation: torch.Tensor,
|
| 288 |
+
signs: torch.Tensor,
|
| 289 |
+
boundaries: torch.Tensor,
|
| 290 |
+
*,
|
| 291 |
+
eps: float = 1e-12,
|
| 292 |
+
inv_sqrt_block: float,
|
| 293 |
+
threads: int = 256,
|
| 294 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 295 |
+
if not x.is_cuda:
|
| 296 |
+
raise RuntimeError("native packed W4 activation quantization requires CUDA tensors")
|
| 297 |
+
if x.dtype not in {torch.float16, torch.bfloat16}:
|
| 298 |
+
raise ValueError("x must be float16 or bfloat16")
|
| 299 |
+
dim = x.shape[-1]
|
| 300 |
+
if dim not in {512, 1024, 2048, 4096, 8192, 16384}:
|
| 301 |
+
raise ValueError(
|
| 302 |
+
"native packed W4 activation quantization supports dimensions "
|
| 303 |
+
"512, 1024, 2048, 4096, 8192, and 16384"
|
| 304 |
+
)
|
| 305 |
+
if threads not in {128, 256, 512}:
|
| 306 |
+
raise ValueError("threads must be 128, 256, or 512")
|
| 307 |
+
if permutation.numel() != dim or signs.numel() != dim:
|
| 308 |
+
raise ValueError("permutation and signs must match the input dimension")
|
| 309 |
+
if boundaries.numel() != 15:
|
| 310 |
+
raise ValueError("boundaries must contain 15 values")
|
| 311 |
+
|
| 312 |
+
original_shape = x.shape
|
| 313 |
+
values = x.contiguous().reshape(-1, dim)
|
| 314 |
+
packed = torch.empty((values.shape[0], dim // 2), device=x.device, dtype=torch.uint8)
|
| 315 |
+
norms = torch.empty(values.shape[0], device=x.device, dtype=torch.float32)
|
| 316 |
+
permutation_dtype = torch.int32 if permutation.dtype == torch.int32 else torch.int64
|
| 317 |
+
permutation_values = permutation.to(device=x.device, dtype=permutation_dtype).contiguous()
|
| 318 |
+
sign_values = signs.to(device=x.device, dtype=torch.int8).contiguous()
|
| 319 |
+
boundary_values = boundaries.to(device=x.device, dtype=torch.float32).contiguous()
|
| 320 |
+
ops.quantize_activations_packed_w4(
|
| 321 |
+
packed,
|
| 322 |
+
norms,
|
| 323 |
+
values,
|
| 324 |
+
permutation_values,
|
| 325 |
+
sign_values,
|
| 326 |
+
boundary_values,
|
| 327 |
+
eps,
|
| 328 |
+
inv_sqrt_block,
|
| 329 |
+
threads,
|
| 330 |
+
)
|
| 331 |
+
return (
|
| 332 |
+
packed.reshape(*original_shape[:-1], dim // 2),
|
| 333 |
+
norms.reshape(original_shape[:-1]),
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def quantize_activations_int8(
|
| 338 |
+
x: torch.Tensor,
|
| 339 |
+
permutation: torch.Tensor,
|
| 340 |
+
signs: torch.Tensor,
|
| 341 |
+
boundaries: torch.Tensor,
|
| 342 |
+
codes: torch.Tensor,
|
| 343 |
+
*,
|
| 344 |
+
eps: float = 1e-12,
|
| 345 |
+
inv_sqrt_block: float,
|
| 346 |
+
threads: int = 256,
|
| 347 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 348 |
+
if not x.is_cuda:
|
| 349 |
+
raise RuntimeError("native INT8 activation quantization requires CUDA tensors")
|
| 350 |
+
if x.dtype not in {torch.float16, torch.bfloat16}:
|
| 351 |
+
raise ValueError("x must be float16 or bfloat16")
|
| 352 |
+
dim = x.shape[-1]
|
| 353 |
+
if dim not in {512, 1024, 2048, 4096, 8192, 12288, 16384}:
|
| 354 |
+
raise ValueError(
|
| 355 |
+
"native INT8 activation quantization supports dimensions "
|
| 356 |
+
"512, 1024, 2048, 4096, 8192, 12288, and 16384"
|
| 357 |
+
)
|
| 358 |
+
if threads not in {128, 256, 512}:
|
| 359 |
+
raise ValueError("threads must be 128, 256, or 512")
|
| 360 |
+
if permutation.numel() != dim or signs.numel() != dim:
|
| 361 |
+
raise ValueError("permutation and signs must match the input dimension")
|
| 362 |
+
if boundaries.numel() != 15:
|
| 363 |
+
raise ValueError("boundaries must contain 15 values")
|
| 364 |
+
if codes.numel() != 16:
|
| 365 |
+
raise ValueError("codes must contain 16 values")
|
| 366 |
+
|
| 367 |
+
original_shape = x.shape
|
| 368 |
+
values = x.contiguous().reshape(-1, dim)
|
| 369 |
+
quantized = torch.empty(values.shape, device=x.device, dtype=torch.int8)
|
| 370 |
+
norms = torch.empty(values.shape[0], device=x.device, dtype=torch.float32)
|
| 371 |
+
permutation_dtype = torch.int32 if permutation.dtype == torch.int32 else torch.int64
|
| 372 |
+
permutation_values = permutation.to(device=x.device, dtype=permutation_dtype).contiguous()
|
| 373 |
+
sign_values = signs.to(device=x.device, dtype=torch.int8).contiguous()
|
| 374 |
+
boundary_values = boundaries.to(device=x.device, dtype=torch.float32).contiguous()
|
| 375 |
+
code_values = codes.to(device=x.device, dtype=torch.int8).contiguous()
|
| 376 |
+
ops.quantize_activations_int8(
|
| 377 |
+
quantized,
|
| 378 |
+
norms,
|
| 379 |
+
values,
|
| 380 |
+
permutation_values,
|
| 381 |
+
sign_values,
|
| 382 |
+
boundary_values,
|
| 383 |
+
code_values,
|
| 384 |
+
eps,
|
| 385 |
+
inv_sqrt_block,
|
| 386 |
+
threads,
|
| 387 |
+
)
|
| 388 |
+
return (
|
| 389 |
+
quantized.reshape(*original_shape[:-1], dim),
|
| 390 |
+
norms.reshape(original_shape[:-1]),
|
| 391 |
+
)
|
torch-ext/torch_binding.cpp
CHANGED
|
@@ -1,8 +1,34 @@
|
|
| 1 |
-
#include <torch/library.h>
|
| 2 |
-
|
| 3 |
#include "registration.h"
|
| 4 |
#include "torch_binding.h"
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
|
| 7 |
ops.def(
|
| 8 |
"matmul_packed_weight(Tensor! out, Tensor x, Tensor packed_weight_indices, "
|
|
@@ -10,9 +36,34 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
|
|
| 10 |
"int out_features, int in_features, int block_m, int block_n, int block_k) -> ()");
|
| 11 |
#if defined(CUDA_KERNEL)
|
| 12 |
ops.impl("matmul_packed_weight", torch::kCUDA, &matmul_packed_weight);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
#elif defined(METAL_KERNEL)
|
| 14 |
ops.impl("matmul_packed_weight", torch::kMPS, &matmul_packed_weight);
|
| 15 |
#endif
|
| 16 |
}
|
|
|
|
| 17 |
|
| 18 |
REGISTER_EXTENSION(TORCH_EXTENSION_NAME)
|
|
|
|
|
|
|
|
|
|
| 1 |
#include "registration.h"
|
| 2 |
#include "torch_binding.h"
|
| 3 |
|
| 4 |
+
#if defined(CPU_KERNEL)
|
| 5 |
+
#include <torch/csrc/stable/library.h>
|
| 6 |
+
|
| 7 |
+
STABLE_TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
|
| 8 |
+
ops.def(
|
| 9 |
+
"matmul_packed_weight(Tensor! out, Tensor x, Tensor packed_weight_indices, "
|
| 10 |
+
"Tensor row_norms, Tensor centroids, Tensor bias, bool has_bias, int bits, "
|
| 11 |
+
"int out_features, int in_features, int block_m, int block_n, int block_k) -> ()");
|
| 12 |
+
ops.def(
|
| 13 |
+
"quantize_activations_cpu(Tensor! out, Tensor x, Tensor permutation, "
|
| 14 |
+
"Tensor signs, Tensor centroids, Tensor boundaries, float eps, "
|
| 15 |
+
"float inv_sqrt_block, int block_size) -> ()");
|
| 16 |
+
ops.def(
|
| 17 |
+
"matmul_packed_adaln_int4_cpu(Tensor! out, Tensor x, Tensor packed_weight, "
|
| 18 |
+
"Tensor scales, Tensor bias, bool has_bias, int out_features, "
|
| 19 |
+
"int in_features, int group_size) -> ()");
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
STABLE_TORCH_LIBRARY_IMPL_EXPAND(TORCH_EXTENSION_NAME, CPU, ops) {
|
| 23 |
+
ops.impl("matmul_packed_weight", TORCH_BOX(&matmul_packed_weight));
|
| 24 |
+
ops.impl("quantize_activations_cpu", TORCH_BOX(&quantize_activations_cpu));
|
| 25 |
+
ops.impl(
|
| 26 |
+
"matmul_packed_adaln_int4_cpu",
|
| 27 |
+
TORCH_BOX(&matmul_packed_adaln_int4_cpu));
|
| 28 |
+
}
|
| 29 |
+
#else
|
| 30 |
+
#include <torch/library.h>
|
| 31 |
+
|
| 32 |
TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
|
| 33 |
ops.def(
|
| 34 |
"matmul_packed_weight(Tensor! out, Tensor x, Tensor packed_weight_indices, "
|
|
|
|
| 36 |
"int out_features, int in_features, int block_m, int block_n, int block_k) -> ()");
|
| 37 |
#if defined(CUDA_KERNEL)
|
| 38 |
ops.impl("matmul_packed_weight", torch::kCUDA, &matmul_packed_weight);
|
| 39 |
+
ops.def(
|
| 40 |
+
"matmul_packed_w4a4_int8(Tensor! out, Tensor packed_activations, "
|
| 41 |
+
"Tensor packed_weight_indices, Tensor token_norms, Tensor row_norms, "
|
| 42 |
+
"Tensor activation_codes, Tensor weight_codes, Tensor bias, bool has_bias, "
|
| 43 |
+
"float activation_scale, float weight_scale, int out_features, "
|
| 44 |
+
"int in_features, int tile_m, int tile_n, bool async_packed, "
|
| 45 |
+
"bool weight_k_major) -> ()");
|
| 46 |
+
ops.impl("matmul_packed_w4a4_int8", torch::kCUDA, &matmul_packed_w4a4_int8);
|
| 47 |
+
ops.def(
|
| 48 |
+
"quantize_activations_packed_w4(Tensor! packed_out, Tensor! norms_out, "
|
| 49 |
+
"Tensor x, Tensor permutation, Tensor signs, Tensor boundaries, float eps, "
|
| 50 |
+
"float inv_sqrt_block, int threads) -> ()");
|
| 51 |
+
ops.impl(
|
| 52 |
+
"quantize_activations_packed_w4",
|
| 53 |
+
torch::kCUDA,
|
| 54 |
+
&quantize_activations_packed_w4);
|
| 55 |
+
ops.def(
|
| 56 |
+
"quantize_activations_int8(Tensor! int8_out, Tensor! norms_out, Tensor x, "
|
| 57 |
+
"Tensor permutation, Tensor signs, Tensor boundaries, Tensor codes, float eps, "
|
| 58 |
+
"float inv_sqrt_block, int threads) -> ()");
|
| 59 |
+
ops.impl(
|
| 60 |
+
"quantize_activations_int8",
|
| 61 |
+
torch::kCUDA,
|
| 62 |
+
&quantize_activations_int8);
|
| 63 |
#elif defined(METAL_KERNEL)
|
| 64 |
ops.impl("matmul_packed_weight", torch::kMPS, &matmul_packed_weight);
|
| 65 |
#endif
|
| 66 |
}
|
| 67 |
+
#endif
|
| 68 |
|
| 69 |
REGISTER_EXTENSION(TORCH_EXTENSION_NAME)
|
torch-ext/torch_binding.h
CHANGED
|
@@ -1,14 +1,20 @@
|
|
| 1 |
#pragma once
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
#include <torch/torch.h>
|
|
|
|
|
|
|
| 4 |
|
| 5 |
void matmul_packed_weight(
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
bool has_bias,
|
| 13 |
int64_t bits,
|
| 14 |
int64_t out_features,
|
|
@@ -16,3 +22,71 @@ void matmul_packed_weight(
|
|
| 16 |
int64_t block_m,
|
| 17 |
int64_t block_n,
|
| 18 |
int64_t block_k);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
#pragma once
|
| 2 |
|
| 3 |
+
#if defined(CPU_KERNEL)
|
| 4 |
+
#include <torch/csrc/stable/tensor.h>
|
| 5 |
+
using OrbitQuantTensor = torch::stable::Tensor;
|
| 6 |
+
#else
|
| 7 |
#include <torch/torch.h>
|
| 8 |
+
using OrbitQuantTensor = torch::Tensor;
|
| 9 |
+
#endif
|
| 10 |
|
| 11 |
void matmul_packed_weight(
|
| 12 |
+
OrbitQuantTensor &out,
|
| 13 |
+
OrbitQuantTensor const &x,
|
| 14 |
+
OrbitQuantTensor const &packed_weight_indices,
|
| 15 |
+
OrbitQuantTensor const &row_norms,
|
| 16 |
+
OrbitQuantTensor const ¢roids,
|
| 17 |
+
OrbitQuantTensor const &bias,
|
| 18 |
bool has_bias,
|
| 19 |
int64_t bits,
|
| 20 |
int64_t out_features,
|
|
|
|
| 22 |
int64_t block_m,
|
| 23 |
int64_t block_n,
|
| 24 |
int64_t block_k);
|
| 25 |
+
|
| 26 |
+
#if defined(CPU_KERNEL)
|
| 27 |
+
void quantize_activations_cpu(
|
| 28 |
+
OrbitQuantTensor &out,
|
| 29 |
+
OrbitQuantTensor const &x,
|
| 30 |
+
OrbitQuantTensor const &permutation,
|
| 31 |
+
OrbitQuantTensor const &signs,
|
| 32 |
+
OrbitQuantTensor const ¢roids,
|
| 33 |
+
OrbitQuantTensor const &boundaries,
|
| 34 |
+
double eps,
|
| 35 |
+
double inv_sqrt_block,
|
| 36 |
+
int64_t block_size);
|
| 37 |
+
|
| 38 |
+
void matmul_packed_adaln_int4_cpu(
|
| 39 |
+
OrbitQuantTensor &out,
|
| 40 |
+
OrbitQuantTensor const &x,
|
| 41 |
+
OrbitQuantTensor const &packed_weight,
|
| 42 |
+
OrbitQuantTensor const &scales,
|
| 43 |
+
OrbitQuantTensor const &bias,
|
| 44 |
+
bool has_bias,
|
| 45 |
+
int64_t out_features,
|
| 46 |
+
int64_t in_features,
|
| 47 |
+
int64_t group_size);
|
| 48 |
+
#endif
|
| 49 |
+
|
| 50 |
+
#if defined(CUDA_KERNEL)
|
| 51 |
+
void matmul_packed_w4a4_int8(
|
| 52 |
+
torch::Tensor &out,
|
| 53 |
+
torch::Tensor const &packed_activations,
|
| 54 |
+
torch::Tensor const &packed_weight_indices,
|
| 55 |
+
torch::Tensor const &token_norms,
|
| 56 |
+
torch::Tensor const &row_norms,
|
| 57 |
+
torch::Tensor const &activation_codes,
|
| 58 |
+
torch::Tensor const &weight_codes,
|
| 59 |
+
torch::Tensor const &bias,
|
| 60 |
+
bool has_bias,
|
| 61 |
+
double activation_scale,
|
| 62 |
+
double weight_scale,
|
| 63 |
+
int64_t out_features,
|
| 64 |
+
int64_t in_features,
|
| 65 |
+
int64_t tile_m,
|
| 66 |
+
int64_t tile_n,
|
| 67 |
+
bool async_packed,
|
| 68 |
+
bool weight_k_major);
|
| 69 |
+
|
| 70 |
+
void quantize_activations_packed_w4(
|
| 71 |
+
torch::Tensor &packed_out,
|
| 72 |
+
torch::Tensor &norms_out,
|
| 73 |
+
torch::Tensor const &x,
|
| 74 |
+
torch::Tensor const &permutation,
|
| 75 |
+
torch::Tensor const &signs,
|
| 76 |
+
torch::Tensor const &boundaries,
|
| 77 |
+
double eps,
|
| 78 |
+
double inv_sqrt_block,
|
| 79 |
+
int64_t threads);
|
| 80 |
+
|
| 81 |
+
void quantize_activations_int8(
|
| 82 |
+
torch::Tensor &int8_out,
|
| 83 |
+
torch::Tensor &norms_out,
|
| 84 |
+
torch::Tensor const &x,
|
| 85 |
+
torch::Tensor const &permutation,
|
| 86 |
+
torch::Tensor const &signs,
|
| 87 |
+
torch::Tensor const &boundaries,
|
| 88 |
+
torch::Tensor const &codes,
|
| 89 |
+
double eps,
|
| 90 |
+
double inv_sqrt_block,
|
| 91 |
+
int64_t threads);
|
| 92 |
+
#endif
|