File size: 7,396 Bytes
f7eb3fa 57c2394 f7eb3fa b050a89 f7eb3fa 57c2394 f7eb3fa 57c2394 f7eb3fa 57c2394 f7eb3fa 57c2394 b050a89 c34d985 b050a89 57c2394 b050a89 c34d985 57c2394 c34d985 b050a89 cb0ceb1 57c2394 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | # OrbitQuant Packed Matmul
Packed low-bit matrix multiplication kernel for OrbitQuant inference.
This kernel consumes OrbitQuant packed weight indices, per-row norms, and Lloyd-Max
centroids directly, avoiding a full BF16/FP16 dequantized weight cache before the
linear projection.
## API
```python
import torch
from orbitquant_packed_matmul import matmul_packed_weight
out = matmul_packed_weight(
x,
packed_weight_indices,
row_norms,
centroids,
bits=4,
out_features=3072,
in_features=3072,
bias=bias,
)
```
Inputs:
- `x`: contiguous or reshapeable tensor with shape `[..., in_features]`.
- `packed_weight_indices`: `uint8` low-bit packed row-major codebook indices.
- `row_norms`: row norms with shape `[out_features]`; CUDA consumes the
artifact's `bfloat16` values directly, while Metal uses `float32` internally.
- `centroids`: `float32` Lloyd-Max centroids with shape `[2**bits]`.
- `bias`: optional projection bias.
`x` may be `float32`, `float16`, or `bfloat16`. The output has shape
`[..., out_features]` and the same dtype as `x`.
The CUDA package also exports the operations used by OrbitQuant's W4A4 runtime:
- `quantize_activations_int8`: token norm, RPBH/FWHT, nearest-codebook
assignment, and INT8-surrogate output in one native launch.
- `quantize_activations_packed_w4`: the same activation path with packed 4-bit
output for the direct packed matmul fallback.
- `matmul_packed_w4a4_int8`: direct packed A4/W4 CUDA MMA with fused token norm,
row norm, surrogate scales, and bias epilogue.
On CUDA compute capability 8.0 or newer, OrbitQuant normally combines
`quantize_activations_int8` with chunked packed-weight decode and Torch's
CUTLASS-backed INT8 matmul. The direct packed MMA operation remains available
when that path is unsupported. Neither path materializes a complete BF16/FP16
weight matrix.
## Build And Test
```bash
nix --option sandbox relaxed --option max-jobs 1 --option cores 8 \
run .#build-and-copy -L
nix --option sandbox relaxed --option max-jobs 1 --option cores 8 \
run .#ci-test -L
```
The build produces ABI3 Hugging Face Kernels artifacts under `build/` for the
supported backend variants on the current platform. On macOS, `sandbox relaxed`
or enabled Nix sandboxing is required by `kernel-builder`. The commands build
local files only; they do not upload to Kernel Hub.
For a faster CUDA-only development build on a machine with a matching Torch and
CUDA toolchain:
```bash
cargo install --git https://github.com/huggingface/kernels hf-kernel-builder
kernel-builder check-config .
kernel-builder create-pyproject -f .
TORCH_CUDA_ARCH_LIST="8.9" CUDACXX=/usr/local/cuda/bin/nvcc \
python setup.py build_kernel
```
For a local Metal build compatible with macOS 15 and newer:
```bash
cargo install --git https://github.com/huggingface/kernels hf-kernel-builder
kernel-builder check-config .
kernel-builder create-pyproject -f .
MACOSX_DEPLOYMENT_TARGET=15.0 \
CMAKE_ARGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=15.0" \
python setup.py build_kernel
kernel-builder check-abi --macos 15.0 --python-abi 3.9 .
```
This generated project is for local testing and must not be committed or
distributed without a successful `kernel-builder check-abi`. Use the Nix build
for redistributable variants.
For direct local imports, add the matching `build/torch*-<backend>-<platform>`
directory to `PYTHONPATH`; the `torch*` variant must match the runtime PyTorch
version:
```bash
export PYTHONPATH="/path/to/build/torch212-metal-aarch64-darwin:$PYTHONPATH"
python -c "import orbitquant_packed_matmul; print(orbitquant_packed_matmul)"
```
For PyTorch 2.9 CUDA inference, set the allocator before starting Python when
minimum reserved memory is important:
```bash
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True python generate.py
```
OrbitQuant detects that importable package before trying any Hub loader. For
Hugging Face `kernels` local loading instead, set `LOCAL_KERNELS` to the same
built variant directory containing `metadata.json`, not to the source package
root:
```bash
export LOCAL_KERNELS="WaveCut/orbitquant-packed-matmul=/path/to/build/torch212-metal-aarch64-darwin"
```
## Benchmark
The benchmark reports two PyTorch references:
- `predequantized_f_linear_seconds_per_iter`: `torch.nn.functional.linear`
over a full dequantized weight matrix that was materialized before timing.
- `dequantize_then_f_linear_seconds_per_iter`: materialize the full
dequantized weight matrix inside each timed iteration, then call
`torch.nn.functional.linear`.
```bash
PYTHONPATH=/path/to/build/torch212-metal-aarch64-darwin \
python benchmarks/benchmark.py \
--device mps \
--bits 4 \
--rows 512 \
--in-features 3072 \
--out-features 3072 \
--iters 20
```
`--rows` accepts a comma-separated sweep (the default covers decode-bound
small batches and GEMM-bound large batches), and `--dtype` selects the
activation dtype explicitly. Headline timings are hot-loop medians.
The script prints JSON with `packed_seconds_per_iter`,
`predequantized_f_linear_seconds_per_iter`,
`dequantize_then_f_linear_seconds_per_iter` (all hot-loop medians), the
per-path `*_hot_mean_seconds`, `*_hot_median_seconds`, and
`*_hot_p95_seconds` distributions,
`packed_vs_predequantized_f_linear_speedup`,
`packed_vs_dequantize_then_f_linear_speedup`, compatibility aliases
`reference_seconds_per_iter` and `packed_vs_reference_speedup`, and
`max_abs_error`.
It also reports storage accounting for the packed weight path:
`packed_weight_indices_bytes`, `row_norms_bytes`, `centroid_bytes`,
`packed_weight_path_bytes`, `materialized_weight_bytes`, and
`packed_weight_path_vs_materialized_weight_ratio`. These values describe only
the weight-side storage used by this operator; they are not end-to-end model
VRAM measurements.
### Metal reference results
Measured on an Apple M2 Max with Torch 2.12.1, FP16 activations, W4 packed
weights, transformer-scale shapes (hot-loop medians over 30 iterations; each
iteration synchronizes, so sub-millisecond rows include the MPS submit
floor):
| Shape (rows x in x out) | Packed Metal | Resident FP16 `F.linear` | Materialize + `F.linear` | Packed vs resident | Packed vs materialize |
| --- | ---: | ---: | ---: | ---: | ---: |
| 1 x 3072 x 3072 | 0.330 ms | 0.200 ms | 1.791 ms | 0.60x | 5.42x |
| 4 x 3072 x 3072 | 0.377 ms | 0.215 ms | 1.768 ms | 0.57x | 4.69x |
| 32 x 3072 x 3072 | 0.367 ms | 0.312 ms | 1.700 ms | 0.85x | 4.63x |
| 512 x 3072 x 3072 | 1.355 ms | 1.077 ms | 2.331 ms | 0.79x | 1.72x |
| 512 x 3072 x 12288 | 5.138 ms | 3.907 ms | 9.312 ms | 0.76x | 1.81x |
| 1 x 3072 x 12288 | 0.417 ms | 0.383 ms | 6.202 ms | 0.92x | 14.88x |
Batches of at most four rows dispatch a skinny-batch GEMV (one simdgroup per
output column, decoded weight segments reused across the batch); larger
batches dispatch the simdgroup-matrix tiles. The packed weight payload, row
norms, and centroids occupy about 25% of the materialized FP16 weight size at
W4. The resident reference excludes weight materialization time and retains
the complete FP16 matrix in memory — it is the throughput ceiling for a
kernel that decodes weights on the fly, not a like-for-like memory
configuration.
End-to-end FLUX.2 Klein 9B measurements and the SDNQ comparison are recorded in
[`docs/flux2-klein-9b-sdnq-vs-orbitquant.md`](../../docs/flux2-klein-9b-sdnq-vs-orbitquant.md).
|