bitsandbytes-cpu-fork 0.50.2.dev2__py3-none-win_amd64.whl → 0.50.2.dev4__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 (54) hide show
  1. bitsandbytes/__init__.py +1 -1
  2. bitsandbytes/__main__.py +4 -4
  3. bitsandbytes/_cpu_caps.py +110 -0
  4. bitsandbytes/_ops.py +526 -526
  5. bitsandbytes/autograd/_functions.py +491 -491
  6. bitsandbytes/backends/cpu/ops.py +805 -805
  7. bitsandbytes/backends/cuda/ops.py +1212 -1212
  8. bitsandbytes/backends/default/ops.py +632 -632
  9. bitsandbytes/backends/hpu/ops.py +53 -53
  10. bitsandbytes/backends/mps/ops.py +277 -277
  11. bitsandbytes/backends/triton/kernels_4bit.py +583 -577
  12. bitsandbytes/backends/triton/kernels_8bit_quant.py +195 -195
  13. bitsandbytes/backends/triton/kernels_optim.py +1177 -1177
  14. bitsandbytes/backends/triton/ops.py +304 -304
  15. bitsandbytes/backends/utils.py +94 -94
  16. bitsandbytes/backends/xpu/ops.py +305 -305
  17. bitsandbytes/cextension.py +447 -405
  18. bitsandbytes/cli.py +50 -50
  19. bitsandbytes/consts.py +12 -12
  20. bitsandbytes/cuda_specs.py +111 -111
  21. bitsandbytes/detect_cpu.py +189 -189
  22. bitsandbytes/diagnostics/cuda.py +193 -193
  23. bitsandbytes/diagnostics/main.py +134 -134
  24. bitsandbytes/diagnostics/utils.py +12 -12
  25. bitsandbytes/disk_balancer.py +1276 -1224
  26. bitsandbytes/efst.py +777 -0
  27. bitsandbytes/functional.py +1836 -1836
  28. bitsandbytes/gdn_cpu.py +616 -602
  29. bitsandbytes/libbitsandbytes_cpu.dll +0 -0
  30. bitsandbytes/nn/__init__.py +19 -19
  31. bitsandbytes/nn/modules.py +1220 -1220
  32. bitsandbytes/nn/parametrize.py +206 -206
  33. bitsandbytes/optim/__init__.py +0 -4
  34. bitsandbytes/optim/adagrad.py +187 -187
  35. bitsandbytes/optim/adam.py +346 -346
  36. bitsandbytes/optim/adamw.py +337 -337
  37. bitsandbytes/optim/ademamix.py +410 -410
  38. bitsandbytes/optim/lamb.py +190 -190
  39. bitsandbytes/optim/lars.py +259 -259
  40. bitsandbytes/optim/lion.py +266 -266
  41. bitsandbytes/optim/optimizer.py +756 -756
  42. bitsandbytes/optim/rmsprop.py +170 -170
  43. bitsandbytes/optim/sgd.py +152 -152
  44. bitsandbytes/torch_cpu_kit.py +468 -468
  45. bitsandbytes/utils.py +208 -208
  46. bitsandbytes_cpu_cli.py +1012 -949
  47. {bitsandbytes_cpu_fork-0.50.2.dev2.dist-info → bitsandbytes_cpu_fork-0.50.2.dev4.dist-info}/METADATA +12 -4
  48. bitsandbytes_cpu_fork-0.50.2.dev4.dist-info/RECORD +64 -0
  49. {bitsandbytes_cpu_fork-0.50.2.dev2.dist-info → bitsandbytes_cpu_fork-0.50.2.dev4.dist-info}/licenses/LICENSE +21 -21
  50. bitsandbytes/optim/adamw4bit.py +0 -183
  51. bitsandbytes_cpu_fork-0.50.2.dev2.dist-info/RECORD +0 -63
  52. {bitsandbytes_cpu_fork-0.50.2.dev2.dist-info → bitsandbytes_cpu_fork-0.50.2.dev4.dist-info}/WHEEL +0 -0
  53. {bitsandbytes_cpu_fork-0.50.2.dev2.dist-info → bitsandbytes_cpu_fork-0.50.2.dev4.dist-info}/entry_points.txt +0 -0
  54. {bitsandbytes_cpu_fork-0.50.2.dev2.dist-info → bitsandbytes_cpu_fork-0.50.2.dev4.dist-info}/top_level.txt +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)