bitsandbytes-cpu-fork 0.50.2.dev0__py3-none-win_amd64.whl → 0.50.2.dev2__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. bitsandbytes/__init__.py +1 -1
  2. bitsandbytes/__main__.py +4 -4
  3. bitsandbytes/_ops.py +526 -526
  4. bitsandbytes/autograd/_functions.py +491 -491
  5. bitsandbytes/backends/cpu/ops.py +805 -805
  6. bitsandbytes/backends/cuda/ops.py +1212 -1212
  7. bitsandbytes/backends/default/ops.py +632 -632
  8. bitsandbytes/backends/hpu/ops.py +53 -53
  9. bitsandbytes/backends/mps/ops.py +277 -277
  10. bitsandbytes/backends/triton/kernels_4bit.py +577 -577
  11. bitsandbytes/backends/triton/kernels_8bit_quant.py +195 -195
  12. bitsandbytes/backends/triton/kernels_optim.py +1177 -1177
  13. bitsandbytes/backends/triton/ops.py +304 -304
  14. bitsandbytes/backends/utils.py +94 -94
  15. bitsandbytes/backends/xpu/ops.py +305 -305
  16. bitsandbytes/cextension.py +405 -405
  17. bitsandbytes/cli.py +50 -0
  18. bitsandbytes/consts.py +12 -12
  19. bitsandbytes/cuda_specs.py +111 -111
  20. bitsandbytes/detect_cpu.py +189 -0
  21. bitsandbytes/diagnostics/cuda.py +193 -193
  22. bitsandbytes/diagnostics/main.py +134 -134
  23. bitsandbytes/diagnostics/utils.py +12 -12
  24. bitsandbytes/disk_balancer.py +1224 -0
  25. bitsandbytes/functional.py +1836 -1836
  26. bitsandbytes/gdn_cpu.py +602 -602
  27. bitsandbytes/libbitsandbytes_cpu.dll +0 -0
  28. bitsandbytes/nn/__init__.py +19 -19
  29. bitsandbytes/nn/modules.py +1220 -1220
  30. bitsandbytes/nn/parametrize.py +206 -206
  31. bitsandbytes/optim/__init__.py +4 -0
  32. bitsandbytes/optim/adagrad.py +187 -187
  33. bitsandbytes/optim/adam.py +346 -346
  34. bitsandbytes/optim/adamw.py +337 -337
  35. bitsandbytes/optim/adamw4bit.py +183 -0
  36. bitsandbytes/optim/ademamix.py +410 -410
  37. bitsandbytes/optim/lamb.py +190 -190
  38. bitsandbytes/optim/lars.py +259 -259
  39. bitsandbytes/optim/lion.py +266 -266
  40. bitsandbytes/optim/optimizer.py +756 -756
  41. bitsandbytes/optim/rmsprop.py +170 -170
  42. bitsandbytes/optim/sgd.py +152 -152
  43. bitsandbytes/torch_cpu_kit.py +468 -0
  44. bitsandbytes/utils.py +208 -208
  45. bitsandbytes_cpu_cli.py +949 -0
  46. {bitsandbytes_cpu_fork-0.50.2.dev0.dist-info → bitsandbytes_cpu_fork-0.50.2.dev2.dist-info}/METADATA +101 -3
  47. bitsandbytes_cpu_fork-0.50.2.dev2.dist-info/RECORD +63 -0
  48. bitsandbytes_cpu_fork-0.50.2.dev2.dist-info/entry_points.txt +3 -0
  49. {bitsandbytes_cpu_fork-0.50.2.dev0.dist-info → bitsandbytes_cpu_fork-0.50.2.dev2.dist-info}/licenses/LICENSE +21 -21
  50. bitsandbytes_cpu_fork-0.50.2.dev2.dist-info/top_level.txt +2 -0
  51. bitsandbytes_cpu_fork-0.50.2.dev0.dist-info/RECORD +0 -56
  52. bitsandbytes_cpu_fork-0.50.2.dev0.dist-info/top_level.txt +0 -1
  53. {bitsandbytes_cpu_fork-0.50.2.dev0.dist-info → bitsandbytes_cpu_fork-0.50.2.dev2.dist-info}/WHEEL +0 -0
@@ -1,53 +1,53 @@
1
- from collections.abc import Sequence
2
- import math
3
-
4
- import torch
5
-
6
- from ..._ops import register_kernel
7
- from ..utils import GAUDI_SW_VER
8
-
9
-
10
- # convert btw standard 4-bit compression format and ipex compression format
11
- # needed for backward compatibility with older versions of gaudi sw
12
- def _reverse_4bit_compress_format(weight: torch.Tensor):
13
- out_1 = (weight & 0xF0) >> 4
14
- out_2 = (weight & 0xF) << 4
15
- out = out_1 | out_2
16
- return out
17
-
18
-
19
- @register_kernel("bitsandbytes::dequantize_4bit", "hpu")
20
- def _(
21
- A: torch.Tensor,
22
- absmax: torch.Tensor,
23
- blocksize: int,
24
- quant_type: str,
25
- shape: Sequence[int],
26
- dtype: torch.dtype,
27
- ) -> torch.Tensor:
28
- if quant_type != "nf4":
29
- raise ValueError(f"HPU backend only supports quant_type 'nf4', got {quant_type!r}")
30
- if A.dtype not in (torch.bfloat16, torch.uint8):
31
- raise ValueError(f"HPU backend only supports uint8 or bfloat16 storage, got {A.dtype}")
32
-
33
- # Enable non uint8 dtype
34
- if A.dtype != torch.uint8:
35
- A = A.view(torch.uint8)
36
-
37
- A = A.reshape(-1)
38
-
39
- if GAUDI_SW_VER and (GAUDI_SW_VER.major < 1 or GAUDI_SW_VER.minor < 22):
40
- A = _reverse_4bit_compress_format(A)
41
-
42
- # HPU dequantization function for NF4 quantized tensors.
43
- out_dq = torch.ops.hpu.dequantize_nf4(
44
- A,
45
- absmax.to(dtype),
46
- blocksize,
47
- out_shape=(math.prod(shape),),
48
- out_dtype=dtype,
49
- )
50
-
51
- output = out_dq.reshape(shape)
52
-
53
- return output
1
+ from collections.abc import Sequence
2
+ import math
3
+
4
+ import torch
5
+
6
+ from ..._ops import register_kernel
7
+ from ..utils import GAUDI_SW_VER
8
+
9
+
10
+ # convert btw standard 4-bit compression format and ipex compression format
11
+ # needed for backward compatibility with older versions of gaudi sw
12
+ def _reverse_4bit_compress_format(weight: torch.Tensor):
13
+ out_1 = (weight & 0xF0) >> 4
14
+ out_2 = (weight & 0xF) << 4
15
+ out = out_1 | out_2
16
+ return out
17
+
18
+
19
+ @register_kernel("bitsandbytes::dequantize_4bit", "hpu")
20
+ def _(
21
+ A: torch.Tensor,
22
+ absmax: torch.Tensor,
23
+ blocksize: int,
24
+ quant_type: str,
25
+ shape: Sequence[int],
26
+ dtype: torch.dtype,
27
+ ) -> torch.Tensor:
28
+ if quant_type != "nf4":
29
+ raise ValueError(f"HPU backend only supports quant_type 'nf4', got {quant_type!r}")
30
+ if A.dtype not in (torch.bfloat16, torch.uint8):
31
+ raise ValueError(f"HPU backend only supports uint8 or bfloat16 storage, got {A.dtype}")
32
+
33
+ # Enable non uint8 dtype
34
+ if A.dtype != torch.uint8:
35
+ A = A.view(torch.uint8)
36
+
37
+ A = A.reshape(-1)
38
+
39
+ if GAUDI_SW_VER and (GAUDI_SW_VER.major < 1 or GAUDI_SW_VER.minor < 22):
40
+ A = _reverse_4bit_compress_format(A)
41
+
42
+ # HPU dequantization function for NF4 quantized tensors.
43
+ out_dq = torch.ops.hpu.dequantize_nf4(
44
+ A,
45
+ absmax.to(dtype),
46
+ blocksize,
47
+ out_shape=(math.prod(shape),),
48
+ out_dtype=dtype,
49
+ )
50
+
51
+ output = out_dq.reshape(shape)
52
+
53
+ return output
@@ -1,277 +1,277 @@
1
- """MPS backend for bitsandbytes quantization ops.
2
-
3
- Hub kernels (kernels-community/bitsandbytes-mps) are attempted lazily on
4
- macOS 26+. On older macOS the hub kernel path is skipped entirely and
5
- default fallbacks or MPS-specific pure PyTorch fallbacks are used for all ops.
6
-
7
- Note: not all ops have implementations on the Hub kernels. Those that do not are also
8
- implemented using pure PyTorch fallbacks.
9
- """
10
-
11
- from collections.abc import Sequence
12
- from math import prod
13
- import platform
14
- from typing import Optional
15
-
16
- import torch
17
-
18
- from ..._ops import register_kernel
19
- from ..default.ops import (
20
- _dequantize_4bit_compute,
21
- _get_4bit_quantize_bounds,
22
- _try_torch_compile,
23
- )
24
- from ..utils import _get_4bit_code
25
-
26
- _QUANT_MAP = {"fp4": 1, "nf4": 2}
27
-
28
- _kernel = None
29
-
30
- _macos_major = int(platform.mac_ver()[0].split(".")[0]) if platform.mac_ver()[0] else 0
31
-
32
- # Pre-set to True on macOS < 26 so _get_kernel() never attempts the import.
33
- _kernel_load_failed = _macos_major < 26
34
-
35
-
36
- def _get_kernel():
37
- global _kernel, _kernel_load_failed
38
- if _kernel_load_failed:
39
- return None
40
- if _kernel is not None:
41
- return _kernel
42
- try:
43
- from kernels import get_kernel
44
-
45
- _kernel = get_kernel("kernels-community/bitsandbytes-mps", version=1)
46
- except Exception:
47
- _kernel_load_failed = True
48
- return None
49
- return _kernel
50
-
51
-
52
- @_try_torch_compile(dynamic=True)
53
- def _quantize_blockwise_compute(
54
- A_flat: torch.Tensor, code: torch.Tensor, blocksize: int
55
- ) -> tuple[torch.Tensor, torch.Tensor]:
56
- """
57
- On torch <= 2.12, torch.bucketize does not perform well.
58
- Implements blockwise quantization using a binary search instead of using the default.
59
- """
60
- n = A_flat.numel()
61
- rem = n % blocksize
62
- full = n - rem
63
- blocks = full // blocksize
64
- A_com = A_flat[:full].reshape(blocks, blocksize)
65
- absmax = A_com.abs().max(dim=-1)[0]
66
- scaled = torch.clamp(A_com * (1.0 / absmax.clamp(min=1e-38).view(-1, 1)), -1, 1).reshape(-1)
67
- if rem:
68
- am = A_flat[full:].abs().max().clamp(min=1e-38)
69
- absmax = torch.cat([absmax, am.unsqueeze(0)])
70
- scaled = torch.cat([scaled, torch.clamp(A_flat[full:] / am, -1, 1)])
71
- bounds = (code[:-1] + code[1:]) / 2
72
- n_bounds = bounds.shape[0]
73
- n_iters = n_bounds.bit_length()
74
- lo = torch.zeros(scaled.shape, dtype=torch.int16, device=scaled.device)
75
- hi = torch.full(scaled.shape, n_bounds, dtype=torch.int16, device=scaled.device)
76
- for _ in range(n_iters):
77
- mid = (lo + hi) >> 1
78
- val = bounds[mid.to(torch.int64)]
79
- lo = torch.where(val < scaled, (mid + 1).to(torch.int16), lo)
80
- hi = torch.where(val >= scaled, mid, hi)
81
- return lo.to(torch.uint8), absmax
82
-
83
-
84
- @register_kernel("bitsandbytes::quantize_blockwise", "mps")
85
- def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
86
- q, absmax = _quantize_blockwise_compute(A.reshape(-1).float(), code.float(), blocksize)
87
- return q.reshape(A.shape), absmax
88
-
89
-
90
- @_try_torch_compile(dynamic=True)
91
- def _quantize_4bit_compute(
92
- A_flat: torch.Tensor,
93
- blocksize: int,
94
- bounds: torch.Tensor,
95
- order: torch.Tensor,
96
- nf4: bool,
97
- ) -> tuple[torch.Tensor, torch.Tensor]:
98
- n = A_flat.numel()
99
- rem = n % blocksize
100
- full = n - rem
101
- blocks = full // blocksize
102
- A_com = A_flat[:full].reshape(blocks, blocksize)
103
- absmax = A_com.abs().max(dim=-1)[0]
104
- scaled = torch.clamp(A_com * (1.0 / absmax.clamp(min=1e-38).view(-1, 1)), -1, 1).reshape(-1)
105
- if rem:
106
- am = A_flat[full:].abs().max().clamp(min=1e-38)
107
- absmax = torch.cat([absmax, am.unsqueeze(0)])
108
- scaled = torch.cat([scaled, torch.clamp(A_flat[full:] / am, -1, 1)])
109
- if scaled.numel() % 2:
110
- scaled = torch.nn.functional.pad(scaled, (0, 1))
111
- idx = torch.zeros(scaled.shape, dtype=torch.int8, device=scaled.device)
112
- for b in bounds:
113
- idx = idx + (scaled > b).to(torch.int8)
114
- if not nf4:
115
- idx = order[idx.to(torch.int32)]
116
- q8 = idx.to(torch.uint8)
117
- return (q8[::2] << 4) | q8[1::2], absmax
118
-
119
-
120
- def _quantize_4bit_fallback(
121
- A: torch.Tensor, blocksize: int, quant_type: str, quant_storage: torch.dtype
122
- ) -> tuple[torch.Tensor, torch.Tensor]:
123
- bounds, order = _get_4bit_quantize_bounds(quant_type, A.device)
124
- packed, absmax = _quantize_4bit_compute(A.reshape(-1).float(), blocksize, bounds, order, quant_type == "nf4")
125
- packed = packed.unsqueeze(1)
126
- if quant_storage != torch.uint8:
127
- packed = packed.squeeze().view(quant_storage).unsqueeze(1)
128
- return packed, absmax
129
-
130
-
131
- @register_kernel("bitsandbytes::quantize_4bit", "mps")
132
- def _(
133
- A: torch.Tensor,
134
- blocksize: int,
135
- quant_type: str,
136
- quant_storage: torch.dtype,
137
- ) -> tuple[torch.Tensor, torch.Tensor]:
138
- if blocksize in (64, 128, 256, 512) and (k := _get_kernel()) is not None:
139
- packed, absmax = k.quantize_4bit(A.contiguous(), blocksize, _QUANT_MAP[quant_type])
140
- packed = packed.view(quant_storage).unsqueeze(1)
141
- return packed, absmax
142
- return _quantize_4bit_fallback(A, blocksize, quant_type, quant_storage)
143
-
144
-
145
- def _dequantize_4bit_impl(
146
- A: torch.Tensor,
147
- absmax: torch.Tensor,
148
- blocksize: int,
149
- quant_type: str,
150
- shape: Sequence[int],
151
- dtype: torch.dtype,
152
- ) -> torch.Tensor:
153
- if A.dtype != torch.uint8:
154
- A = A.view(torch.uint8)
155
-
156
- # Use HF Hub kernel when supported.
157
- if blocksize in (64, 128, 256, 512) and (k := _get_kernel()) is not None:
158
- numel = prod(shape)
159
- out = k.dequantize_4bit(A, absmax, blocksize, _QUANT_MAP[quant_type], numel, dtype)
160
- return out.reshape(shape)
161
-
162
- # Fallback to implementation from default backend.
163
- code = _get_4bit_code(quant_type, A.device)
164
- return _dequantize_4bit_compute(A.reshape(-1), absmax, code, blocksize, shape, dtype)
165
-
166
-
167
- @register_kernel("bitsandbytes::dequantize_4bit", "mps")
168
- def _(
169
- A: torch.Tensor,
170
- absmax: torch.Tensor,
171
- blocksize: int,
172
- quant_type: str,
173
- shape: Sequence[int],
174
- dtype: torch.dtype,
175
- ) -> torch.Tensor:
176
- return _dequantize_4bit_impl(A, absmax, blocksize, quant_type, shape, dtype)
177
-
178
-
179
- @register_kernel("bitsandbytes::dequantize_4bit.out", "mps")
180
- def _(
181
- A: torch.Tensor,
182
- absmax: torch.Tensor,
183
- blocksize: int,
184
- quant_type: str,
185
- shape: Sequence[int],
186
- dtype: torch.dtype,
187
- out: torch.Tensor,
188
- ) -> None:
189
- result = _dequantize_4bit_impl(A, absmax, blocksize, quant_type, shape, dtype)
190
- out.copy_(result)
191
-
192
-
193
- def _gemv_4bit_impl(
194
- A: torch.Tensor,
195
- B: torch.Tensor,
196
- shapeB: Sequence[int],
197
- absmax: torch.Tensor,
198
- code: torch.Tensor,
199
- blocksize: int,
200
- ) -> torch.Tensor:
201
- if blocksize in (64, 128, 256) and (k := _get_kernel()) is not None:
202
- if B.dtype != torch.uint8:
203
- B = B.view(torch.uint8)
204
-
205
- output_features = shapeB[0]
206
- quant_type_int = _QUANT_MAP["fp4"] if code[1] > 0 else _QUANT_MAP["nf4"]
207
-
208
- return k.gemv_4bit(A, B, absmax, output_features, blocksize, quant_type_int)
209
-
210
- quant_type = "fp4" if code[1] > 0 else "nf4"
211
- B_dq = _dequantize_4bit_impl(B, absmax, blocksize, quant_type, shapeB, A.dtype)
212
- return torch.nn.functional.linear(A, B_dq)
213
-
214
-
215
- @register_kernel("bitsandbytes::gemv_4bit", "mps")
216
- def _(
217
- A: torch.Tensor,
218
- B: torch.Tensor,
219
- shapeB: Sequence[int],
220
- absmax: torch.Tensor,
221
- code: torch.Tensor,
222
- blocksize: int,
223
- ) -> torch.Tensor:
224
- return _gemv_4bit_impl(A, B, shapeB, absmax, code, blocksize)
225
-
226
-
227
- @register_kernel("bitsandbytes::gemv_4bit.out", "mps")
228
- def _(
229
- A: torch.Tensor,
230
- B: torch.Tensor,
231
- shapeB: Sequence[int],
232
- absmax: torch.Tensor,
233
- code: torch.Tensor,
234
- blocksize: int,
235
- out: torch.Tensor,
236
- ) -> None:
237
- result = _gemv_4bit_impl(A, B, shapeB, absmax, code, blocksize)
238
- out.copy_(result)
239
-
240
-
241
- @register_kernel("bitsandbytes::gemm_4bit", "mps")
242
- def _(
243
- A: torch.Tensor,
244
- B: torch.Tensor,
245
- shapeB: Sequence[int],
246
- absmax: torch.Tensor,
247
- blocksize: int,
248
- quant_type: str,
249
- bias: Optional[torch.Tensor] = None,
250
- absmax_8bit: Optional[torch.Tensor] = None,
251
- absmax_code: Optional[torch.Tensor] = None,
252
- absmax_offset: Optional[torch.Tensor] = None,
253
- ) -> torch.Tensor:
254
- K = A.shape[-1]
255
- M = A.numel() // K
256
- N = shapeB[0]
257
-
258
- # For nested absmax, we don't have a fused implementation yet.
259
- # Dequantize the absmax values first.
260
- if absmax_8bit is not None:
261
- absmax = (
262
- torch.ops.bitsandbytes.dequantize_blockwise.default(absmax_8bit, absmax, absmax_code, 256, torch.float32)
263
- + absmax_offset
264
- )
265
-
266
- # Use HF Hub kernel when supported for GEMV.
267
- if M == 1 and blocksize in (64, 128, 256) and (k := _get_kernel()) is not None:
268
- if B.dtype != torch.uint8:
269
- B = B.view(torch.uint8)
270
- result = k.gemv_4bit(A, B, absmax.view(N, -1), N, blocksize, _QUANT_MAP[quant_type])
271
- if bias is not None:
272
- result = result + bias
273
- return result
274
-
275
- # Fallback: dequantize + linear.
276
- B_dq = _dequantize_4bit_impl(B, absmax, blocksize, quant_type, shapeB, A.dtype)
277
- return torch.nn.functional.linear(A, B_dq, bias)
1
+ """MPS backend for bitsandbytes quantization ops.
2
+
3
+ Hub kernels (kernels-community/bitsandbytes-mps) are attempted lazily on
4
+ macOS 26+. On older macOS the hub kernel path is skipped entirely and
5
+ default fallbacks or MPS-specific pure PyTorch fallbacks are used for all ops.
6
+
7
+ Note: not all ops have implementations on the Hub kernels. Those that do not are also
8
+ implemented using pure PyTorch fallbacks.
9
+ """
10
+
11
+ from collections.abc import Sequence
12
+ from math import prod
13
+ import platform
14
+ from typing import Optional
15
+
16
+ import torch
17
+
18
+ from ..._ops import register_kernel
19
+ from ..default.ops import (
20
+ _dequantize_4bit_compute,
21
+ _get_4bit_quantize_bounds,
22
+ _try_torch_compile,
23
+ )
24
+ from ..utils import _get_4bit_code
25
+
26
+ _QUANT_MAP = {"fp4": 1, "nf4": 2}
27
+
28
+ _kernel = None
29
+
30
+ _macos_major = int(platform.mac_ver()[0].split(".")[0]) if platform.mac_ver()[0] else 0
31
+
32
+ # Pre-set to True on macOS < 26 so _get_kernel() never attempts the import.
33
+ _kernel_load_failed = _macos_major < 26
34
+
35
+
36
+ def _get_kernel():
37
+ global _kernel, _kernel_load_failed
38
+ if _kernel_load_failed:
39
+ return None
40
+ if _kernel is not None:
41
+ return _kernel
42
+ try:
43
+ from kernels import get_kernel
44
+
45
+ _kernel = get_kernel("kernels-community/bitsandbytes-mps", version=1)
46
+ except Exception:
47
+ _kernel_load_failed = True
48
+ return None
49
+ return _kernel
50
+
51
+
52
+ @_try_torch_compile(dynamic=True)
53
+ def _quantize_blockwise_compute(
54
+ A_flat: torch.Tensor, code: torch.Tensor, blocksize: int
55
+ ) -> tuple[torch.Tensor, torch.Tensor]:
56
+ """
57
+ On torch <= 2.12, torch.bucketize does not perform well.
58
+ Implements blockwise quantization using a binary search instead of using the default.
59
+ """
60
+ n = A_flat.numel()
61
+ rem = n % blocksize
62
+ full = n - rem
63
+ blocks = full // blocksize
64
+ A_com = A_flat[:full].reshape(blocks, blocksize)
65
+ absmax = A_com.abs().max(dim=-1)[0]
66
+ scaled = torch.clamp(A_com * (1.0 / absmax.clamp(min=1e-38).view(-1, 1)), -1, 1).reshape(-1)
67
+ if rem:
68
+ am = A_flat[full:].abs().max().clamp(min=1e-38)
69
+ absmax = torch.cat([absmax, am.unsqueeze(0)])
70
+ scaled = torch.cat([scaled, torch.clamp(A_flat[full:] / am, -1, 1)])
71
+ bounds = (code[:-1] + code[1:]) / 2
72
+ n_bounds = bounds.shape[0]
73
+ n_iters = n_bounds.bit_length()
74
+ lo = torch.zeros(scaled.shape, dtype=torch.int16, device=scaled.device)
75
+ hi = torch.full(scaled.shape, n_bounds, dtype=torch.int16, device=scaled.device)
76
+ for _ in range(n_iters):
77
+ mid = (lo + hi) >> 1
78
+ val = bounds[mid.to(torch.int64)]
79
+ lo = torch.where(val < scaled, (mid + 1).to(torch.int16), lo)
80
+ hi = torch.where(val >= scaled, mid, hi)
81
+ return lo.to(torch.uint8), absmax
82
+
83
+
84
+ @register_kernel("bitsandbytes::quantize_blockwise", "mps")
85
+ def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
86
+ q, absmax = _quantize_blockwise_compute(A.reshape(-1).float(), code.float(), blocksize)
87
+ return q.reshape(A.shape), absmax
88
+
89
+
90
+ @_try_torch_compile(dynamic=True)
91
+ def _quantize_4bit_compute(
92
+ A_flat: torch.Tensor,
93
+ blocksize: int,
94
+ bounds: torch.Tensor,
95
+ order: torch.Tensor,
96
+ nf4: bool,
97
+ ) -> tuple[torch.Tensor, torch.Tensor]:
98
+ n = A_flat.numel()
99
+ rem = n % blocksize
100
+ full = n - rem
101
+ blocks = full // blocksize
102
+ A_com = A_flat[:full].reshape(blocks, blocksize)
103
+ absmax = A_com.abs().max(dim=-1)[0]
104
+ scaled = torch.clamp(A_com * (1.0 / absmax.clamp(min=1e-38).view(-1, 1)), -1, 1).reshape(-1)
105
+ if rem:
106
+ am = A_flat[full:].abs().max().clamp(min=1e-38)
107
+ absmax = torch.cat([absmax, am.unsqueeze(0)])
108
+ scaled = torch.cat([scaled, torch.clamp(A_flat[full:] / am, -1, 1)])
109
+ if scaled.numel() % 2:
110
+ scaled = torch.nn.functional.pad(scaled, (0, 1))
111
+ idx = torch.zeros(scaled.shape, dtype=torch.int8, device=scaled.device)
112
+ for b in bounds:
113
+ idx = idx + (scaled > b).to(torch.int8)
114
+ if not nf4:
115
+ idx = order[idx.to(torch.int32)]
116
+ q8 = idx.to(torch.uint8)
117
+ return (q8[::2] << 4) | q8[1::2], absmax
118
+
119
+
120
+ def _quantize_4bit_fallback(
121
+ A: torch.Tensor, blocksize: int, quant_type: str, quant_storage: torch.dtype
122
+ ) -> tuple[torch.Tensor, torch.Tensor]:
123
+ bounds, order = _get_4bit_quantize_bounds(quant_type, A.device)
124
+ packed, absmax = _quantize_4bit_compute(A.reshape(-1).float(), blocksize, bounds, order, quant_type == "nf4")
125
+ packed = packed.unsqueeze(1)
126
+ if quant_storage != torch.uint8:
127
+ packed = packed.squeeze().view(quant_storage).unsqueeze(1)
128
+ return packed, absmax
129
+
130
+
131
+ @register_kernel("bitsandbytes::quantize_4bit", "mps")
132
+ def _(
133
+ A: torch.Tensor,
134
+ blocksize: int,
135
+ quant_type: str,
136
+ quant_storage: torch.dtype,
137
+ ) -> tuple[torch.Tensor, torch.Tensor]:
138
+ if blocksize in (64, 128, 256, 512) and (k := _get_kernel()) is not None:
139
+ packed, absmax = k.quantize_4bit(A.contiguous(), blocksize, _QUANT_MAP[quant_type])
140
+ packed = packed.view(quant_storage).unsqueeze(1)
141
+ return packed, absmax
142
+ return _quantize_4bit_fallback(A, blocksize, quant_type, quant_storage)
143
+
144
+
145
+ def _dequantize_4bit_impl(
146
+ A: torch.Tensor,
147
+ absmax: torch.Tensor,
148
+ blocksize: int,
149
+ quant_type: str,
150
+ shape: Sequence[int],
151
+ dtype: torch.dtype,
152
+ ) -> torch.Tensor:
153
+ if A.dtype != torch.uint8:
154
+ A = A.view(torch.uint8)
155
+
156
+ # Use HF Hub kernel when supported.
157
+ if blocksize in (64, 128, 256, 512) and (k := _get_kernel()) is not None:
158
+ numel = prod(shape)
159
+ out = k.dequantize_4bit(A, absmax, blocksize, _QUANT_MAP[quant_type], numel, dtype)
160
+ return out.reshape(shape)
161
+
162
+ # Fallback to implementation from default backend.
163
+ code = _get_4bit_code(quant_type, A.device)
164
+ return _dequantize_4bit_compute(A.reshape(-1), absmax, code, blocksize, shape, dtype)
165
+
166
+
167
+ @register_kernel("bitsandbytes::dequantize_4bit", "mps")
168
+ def _(
169
+ A: torch.Tensor,
170
+ absmax: torch.Tensor,
171
+ blocksize: int,
172
+ quant_type: str,
173
+ shape: Sequence[int],
174
+ dtype: torch.dtype,
175
+ ) -> torch.Tensor:
176
+ return _dequantize_4bit_impl(A, absmax, blocksize, quant_type, shape, dtype)
177
+
178
+
179
+ @register_kernel("bitsandbytes::dequantize_4bit.out", "mps")
180
+ def _(
181
+ A: torch.Tensor,
182
+ absmax: torch.Tensor,
183
+ blocksize: int,
184
+ quant_type: str,
185
+ shape: Sequence[int],
186
+ dtype: torch.dtype,
187
+ out: torch.Tensor,
188
+ ) -> None:
189
+ result = _dequantize_4bit_impl(A, absmax, blocksize, quant_type, shape, dtype)
190
+ out.copy_(result)
191
+
192
+
193
+ def _gemv_4bit_impl(
194
+ A: torch.Tensor,
195
+ B: torch.Tensor,
196
+ shapeB: Sequence[int],
197
+ absmax: torch.Tensor,
198
+ code: torch.Tensor,
199
+ blocksize: int,
200
+ ) -> torch.Tensor:
201
+ if blocksize in (64, 128, 256) and (k := _get_kernel()) is not None:
202
+ if B.dtype != torch.uint8:
203
+ B = B.view(torch.uint8)
204
+
205
+ output_features = shapeB[0]
206
+ quant_type_int = _QUANT_MAP["fp4"] if code[1] > 0 else _QUANT_MAP["nf4"]
207
+
208
+ return k.gemv_4bit(A, B, absmax, output_features, blocksize, quant_type_int)
209
+
210
+ quant_type = "fp4" if code[1] > 0 else "nf4"
211
+ B_dq = _dequantize_4bit_impl(B, absmax, blocksize, quant_type, shapeB, A.dtype)
212
+ return torch.nn.functional.linear(A, B_dq)
213
+
214
+
215
+ @register_kernel("bitsandbytes::gemv_4bit", "mps")
216
+ def _(
217
+ A: torch.Tensor,
218
+ B: torch.Tensor,
219
+ shapeB: Sequence[int],
220
+ absmax: torch.Tensor,
221
+ code: torch.Tensor,
222
+ blocksize: int,
223
+ ) -> torch.Tensor:
224
+ return _gemv_4bit_impl(A, B, shapeB, absmax, code, blocksize)
225
+
226
+
227
+ @register_kernel("bitsandbytes::gemv_4bit.out", "mps")
228
+ def _(
229
+ A: torch.Tensor,
230
+ B: torch.Tensor,
231
+ shapeB: Sequence[int],
232
+ absmax: torch.Tensor,
233
+ code: torch.Tensor,
234
+ blocksize: int,
235
+ out: torch.Tensor,
236
+ ) -> None:
237
+ result = _gemv_4bit_impl(A, B, shapeB, absmax, code, blocksize)
238
+ out.copy_(result)
239
+
240
+
241
+ @register_kernel("bitsandbytes::gemm_4bit", "mps")
242
+ def _(
243
+ A: torch.Tensor,
244
+ B: torch.Tensor,
245
+ shapeB: Sequence[int],
246
+ absmax: torch.Tensor,
247
+ blocksize: int,
248
+ quant_type: str,
249
+ bias: Optional[torch.Tensor] = None,
250
+ absmax_8bit: Optional[torch.Tensor] = None,
251
+ absmax_code: Optional[torch.Tensor] = None,
252
+ absmax_offset: Optional[torch.Tensor] = None,
253
+ ) -> torch.Tensor:
254
+ K = A.shape[-1]
255
+ M = A.numel() // K
256
+ N = shapeB[0]
257
+
258
+ # For nested absmax, we don't have a fused implementation yet.
259
+ # Dequantize the absmax values first.
260
+ if absmax_8bit is not None:
261
+ absmax = (
262
+ torch.ops.bitsandbytes.dequantize_blockwise.default(absmax_8bit, absmax, absmax_code, 256, torch.float32)
263
+ + absmax_offset
264
+ )
265
+
266
+ # Use HF Hub kernel when supported for GEMV.
267
+ if M == 1 and blocksize in (64, 128, 256) and (k := _get_kernel()) is not None:
268
+ if B.dtype != torch.uint8:
269
+ B = B.view(torch.uint8)
270
+ result = k.gemv_4bit(A, B, absmax.view(N, -1), N, blocksize, _QUANT_MAP[quant_type])
271
+ if bias is not None:
272
+ result = result + bias
273
+ return result
274
+
275
+ # Fallback: dequantize + linear.
276
+ B_dq = _dequantize_4bit_impl(B, absmax, blocksize, quant_type, shapeB, A.dtype)
277
+ return torch.nn.functional.linear(A, B_dq, bias)