WaveCut commited on
Commit
57c2394
·
verified ·
1 Parent(s): cb0ceb1

Sync the source snapshot with iamwavecut/OrbitQuant@2d586f189ab8

Browse files

Tracked 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 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`: `float32` row norms with shape `[out_features]`.
 
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 run .#build-and-copy -L
42
- nix --option sandbox relaxed run .#ci-test -L
 
 
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. For Hugging Face `kernels` local loading, set `LOCAL_KERNELS` to the
52
- same built variant directory containing `metadata.json`, not to the source
53
- package root:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, iters: int, fn) -> float:
32
  _synchronize(device)
33
- start = time.perf_counter()
34
- for _ in range(iters):
35
- fn()
36
  _synchronize(device)
37
- return (time.perf_counter() - start) / iters
38
 
39
 
40
- def main() -> None:
41
- parser = argparse.ArgumentParser()
42
- parser.add_argument("--device", choices=["cuda", "mps"], default="cuda")
43
- parser.add_argument("--bits", type=int, default=4)
44
- parser.add_argument("--rows", type=int, default=4096)
45
- parser.add_argument("--in-features", type=int, default=3072)
46
- parser.add_argument("--out-features", type=int, default=3072)
47
- parser.add_argument("--iters", type=int, default=20)
48
- parser.add_argument("--warmup", type=int, default=3)
49
- parser.add_argument("--seed", type=int, default=0)
50
- parser.add_argument("--with-bias", action="store_true")
51
- args = parser.parse_args()
52
 
53
- torch.manual_seed(args.seed)
54
- dtype = torch.float16 if args.device == "mps" else torch.bfloat16
55
- x = torch.randn(args.rows, args.in_features, device=args.device, dtype=dtype)
56
- indices = torch.randint(
57
- 0,
58
- 2**args.bits,
59
- (args.out_features, args.in_features),
60
- dtype=torch.uint8,
61
- )
62
- packed = _pack(indices, args.bits).to(args.device)
63
- row_norms = torch.ones(args.out_features, device=args.device)
64
- centroids = torch.linspace(-1.0, 1.0, 2**args.bits, device=args.device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- packed_seconds = _time_call(args.device, args.iters, packed_call)
106
- predequantized_linear_seconds = _time_call(
107
  args.device,
108
  args.iters,
109
  predequantized_linear_call,
110
  )
111
- dequantize_then_linear_seconds = _time_call(
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
- max_abs_error = (packed_output.float() - reference_output.float()).abs().max().item()
 
 
 
 
121
 
122
- payload = {
123
  "device": args.device,
124
- "device_name": torch.cuda.get_device_name(0) if args.device == "cuda" else "mps",
 
 
 
 
 
 
 
125
  "dtype": str(dtype).replace("torch.", ""),
126
  "bits": args.bits,
127
- "rows": args.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
- print(json.dumps(payload, indent=2, sort_keys=True))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 &centroids,
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 &centroids,
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
- float const *__restrict__ row_norms,
53
  float const *__restrict__ centroids,
54
- float const *__restrict__ bias,
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
- float const *__restrict__ row_norms,
148
  float const *__restrict__ centroids,
149
- float const *__restrict__ bias,
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
- float const *__restrict__ row_norms,
242
  float const *__restrict__ centroids,
243
- float const *__restrict__ bias,
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 = output_valid && has_bias ? bias[col] : 0.0f;
 
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(row_norms.scalar_type() == torch::kFloat, "row_norms must be float32");
 
 
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() == torch::kFloat, "bias must be float32");
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
- static_cast<int>(std::min<int64_t>(std::max<int64_t>(block_m, 1), 1024 / threads_n));
 
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<float>(), \
386
  centroids.data_ptr<float>(), \
387
- has_bias ? bias.data_ptr<float>() : nullptr, \
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<float>(), \
438
  centroids.data_ptr<float>(), \
439
- has_bias ? bias.data_ptr<float>() : nullptr, \
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<float>(),
482
  centroids.data_ptr<float>(),
483
- has_bias ? bias.data_ptr<float>() : nullptr,
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
- inline float bf16_to_float(ushort value) {
15
- return as_type<float>(uint(value) << 16);
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- inline ushort float_to_bf16(float value) {
19
- const uint bits = as_type<uint>(value);
20
- const uint lsb = (bits >> 16) & 1u;
21
- return ushort((bits + 0x7fffu + lsb) >> 16);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
 
24
  template <typename scalar_t>
@@ -102,83 +208,214 @@ inline void packed_matmul_tiled_value(
102
  }
103
  }
104
 
105
- inline void packed_matmul_tiled_value_bfloat16(
106
- device ushort *out,
107
- device const ushort *x,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 &params,
113
- threadgroup float *shared,
114
- uint2 tid,
115
- uint2 local_tid,
116
- uint2 threads_per_group) {
117
- const long col = tid.x;
118
- const long row = tid.y;
119
- const bool output_valid = row < params.rows && col < params.out_features;
120
- const long block_k = params.block_k;
121
- threadgroup float *x_tile = shared;
122
- threadgroup float *w_tile = shared + long(threads_per_group.y) * block_k;
123
- const long local_col = local_tid.x;
124
- const long local_row = local_tid.y;
125
- const long thread_linear = local_row * long(threads_per_group.x) + local_col;
126
- const long thread_count = long(threads_per_group.x) * long(threads_per_group.y);
127
-
128
- const uint mask = (1u << uint(params.bits)) - 1u;
129
- float acc = output_valid && params.has_bias != 0 ? bias[col] : 0.0f;
130
 
131
- for (long k_start = 0; k_start < params.in_features; k_start += block_k) {
132
- const long x_tile_values = long(threads_per_group.y) * block_k;
133
- for (long offset = thread_linear; offset < x_tile_values; offset += thread_count) {
134
- const long tile_row = offset / block_k;
135
- const long tile_k = offset - tile_row * block_k;
136
- const long global_row = long(tid.y) - local_row + tile_row;
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
- const long w_tile_values = block_k * long(threads_per_group.x);
146
- for (long offset = thread_linear; offset < w_tile_values; offset += thread_count) {
147
- const long tile_k = offset / long(threads_per_group.x);
148
- const long tile_col = offset - tile_k * long(threads_per_group.x);
149
- const long global_k = k_start + tile_k;
150
- const long global_col = long(tid.x) - local_col + tile_col;
151
- float value = 0.0f;
152
- if (global_col < params.out_features && global_k < params.in_features) {
153
- const long value_offset = global_col * params.in_features + global_k;
154
- const long bit_start = value_offset * params.bits;
155
- const long byte_index = bit_start >> 3;
156
- const long bit_offset = bit_start & 7;
157
- uint raw = packed_weight_indices[byte_index];
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
- if (output_valid) {
170
- for (long tile_k = 0; tile_k < block_k && k_start + tile_k < params.in_features;
171
- ++tile_k) {
172
- acc += x_tile[local_row * block_k + tile_k] *
173
- w_tile[tile_k * long(threads_per_group.x) + local_col];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  }
175
  }
176
-
177
- threadgroup_barrier(mem_flags::mem_threadgroup);
178
  }
179
 
180
- if (output_valid) {
181
- out[row * params.out_features + col] = float_to_bf16(acc);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 packed_matmul_forward_bfloat16(
238
- device ushort *out [[buffer(0)]],
239
- device const ushort *x [[buffer(1)]],
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
- packed_matmul_tiled_value_bfloat16(
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 &params,
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 &params,
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 &params [[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 &params [[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 &params [[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(cache, x.scalar_type());
 
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:&params length:sizeof(params) atIndex:6];
143
 
144
- const NSUInteger threads_x = std::min<int64_t>(std::max<int64_t>(block_n, 1), 32);
145
- const NSUInteger threads_y = std::min<int64_t>(std::max<int64_t>(block_m, 1), 32);
146
- const NSUInteger tile_k = std::min<int64_t>(std::max<int64_t>(block_k, 1), 128);
147
- const NSUInteger shared_bytes = (threads_y * tile_k + tile_k * threads_x) * sizeof(float);
148
- [encoder setThreadgroupMemoryLength:shared_bytes atIndex:0];
149
- MTLSize grid_size = MTLSizeMake(out_features, x.size(0), 1);
150
- MTLSize threadgroup_size = MTLSizeMake(threads_x, threads_y, 1);
151
- [encoder dispatchThreads:grid_size threadsPerThreadgroup:threadgroup_size];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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:&params 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 matmul_packed_weight
 
 
 
 
 
 
 
 
 
 
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
- pytest.skip("CUDA or MPS is required")
 
 
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
- torch.arange(out_features * in_features, dtype=torch.uint8).reshape(
58
- out_features, in_features
59
- )
60
- % (2**bits)
61
- )
62
  packed = _pack(indices, bits).to(device)
63
- row_norms = torch.linspace(0.5, 1.5, out_features, device=device)
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
- torch.arange(out_features * in_features, dtype=torch.uint8).reshape(
100
- out_features, in_features
101
- )
102
- % (2**bits)
103
- )
104
  packed = _pack(indices, bits).to(device)
105
- row_norms = torch.linspace(0.5, 1.5, out_features, device=device)
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
- torch.arange(out_features * in_features, dtype=torch.uint8).reshape(
141
- out_features, in_features
142
- )
143
- % (2**bits)
144
- )
145
  packed = _pack(indices, bits).to(device)
146
- row_norms = torch.linspace(0.5, 1.5, out_features, device=device)
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__ = ["matmul_packed_weight"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = 32,
21
  block_n: int = 64,
22
- block_k: int = 64,
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
- norms = row_norms.to(device=x.device, dtype=torch.float32).contiguous()
 
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
- bias_values = bias.to(device=x.device, dtype=torch.float32).contiguous()
 
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
- torch::Tensor &out,
7
- torch::Tensor const &x,
8
- torch::Tensor const &packed_weight_indices,
9
- torch::Tensor const &row_norms,
10
- torch::Tensor const &centroids,
11
- torch::Tensor const &bias,
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 &centroids,
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 &centroids,
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