bitsandbytes-cpu-fork 0.50.2.dev0__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.
- bitsandbytes/__init__.py +81 -0
- bitsandbytes/__main__.py +4 -0
- bitsandbytes/_ops.py +526 -0
- bitsandbytes/autograd/__init__.py +0 -0
- bitsandbytes/autograd/_functions.py +491 -0
- bitsandbytes/backends/__init__.py +0 -0
- bitsandbytes/backends/cpu/__init__.py +0 -0
- bitsandbytes/backends/cpu/ops.py +805 -0
- bitsandbytes/backends/cuda/__init__.py +0 -0
- bitsandbytes/backends/cuda/ops.py +1212 -0
- bitsandbytes/backends/default/__init__.py +0 -0
- bitsandbytes/backends/default/ops.py +632 -0
- bitsandbytes/backends/hpu/__init__.py +0 -0
- bitsandbytes/backends/hpu/ops.py +53 -0
- bitsandbytes/backends/mps/__init__.py +0 -0
- bitsandbytes/backends/mps/ops.py +277 -0
- bitsandbytes/backends/triton/__init__.py +0 -0
- bitsandbytes/backends/triton/kernels_4bit.py +577 -0
- bitsandbytes/backends/triton/kernels_8bit_quant.py +195 -0
- bitsandbytes/backends/triton/kernels_optim.py +1177 -0
- bitsandbytes/backends/triton/ops.py +304 -0
- bitsandbytes/backends/utils.py +94 -0
- bitsandbytes/backends/xpu/__init__.py +0 -0
- bitsandbytes/backends/xpu/ops.py +305 -0
- bitsandbytes/cextension.py +405 -0
- bitsandbytes/consts.py +12 -0
- bitsandbytes/cuda_specs.py +111 -0
- bitsandbytes/diagnostics/__init__.py +0 -0
- bitsandbytes/diagnostics/cuda.py +193 -0
- bitsandbytes/diagnostics/main.py +134 -0
- bitsandbytes/diagnostics/utils.py +12 -0
- bitsandbytes/functional.py +1836 -0
- bitsandbytes/gdn_cpu.py +602 -0
- bitsandbytes/libbitsandbytes_cpu.dll +0 -0
- bitsandbytes/nn/__init__.py +19 -0
- bitsandbytes/nn/modules.py +1220 -0
- bitsandbytes/nn/parametrize.py +206 -0
- bitsandbytes/optim/__init__.py +22 -0
- bitsandbytes/optim/adagrad.py +187 -0
- bitsandbytes/optim/adam.py +346 -0
- bitsandbytes/optim/adamw.py +337 -0
- bitsandbytes/optim/ademamix.py +410 -0
- bitsandbytes/optim/lamb.py +190 -0
- bitsandbytes/optim/lars.py +259 -0
- bitsandbytes/optim/lion.py +266 -0
- bitsandbytes/optim/optimizer.py +756 -0
- bitsandbytes/optim/rmsprop.py +170 -0
- bitsandbytes/optim/sgd.py +152 -0
- bitsandbytes/py.typed +0 -0
- bitsandbytes/utils.py +208 -0
- bitsandbytes/vcomp140.dll +0 -0
- bitsandbytes_cpu_fork-0.50.2.dev0.dist-info/METADATA +346 -0
- bitsandbytes_cpu_fork-0.50.2.dev0.dist-info/RECORD +56 -0
- bitsandbytes_cpu_fork-0.50.2.dev0.dist-info/WHEEL +5 -0
- bitsandbytes_cpu_fork-0.50.2.dev0.dist-info/licenses/LICENSE +21 -0
- bitsandbytes_cpu_fork-0.50.2.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
import ctypes as ct
|
|
3
|
+
import logging
|
|
4
|
+
import math
|
|
5
|
+
from math import prod
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
from bitsandbytes.functional import get_ptr, has_avx512bf16
|
|
11
|
+
|
|
12
|
+
from ..._ops import register_kernel
|
|
13
|
+
from ...cextension import ErrorHandlerMockBNBNativeLibrary, lib
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# 256 项 fp32 8-bit 码表缓存(见 _gemm_8bit_cpu 的说明)
|
|
18
|
+
_GEMM_8BIT_CODE = None
|
|
19
|
+
|
|
20
|
+
_has_avx512 = torch.backends.cpu.get_cpu_capability() == "AVX512"
|
|
21
|
+
|
|
22
|
+
# torch._int_mm for s8@s8->s32 is supported on CPU from torch 2.4+.
|
|
23
|
+
# However, we can overflow if we use this without AVX512_VNNI support.
|
|
24
|
+
# This is fixed in torch 2.6+, so we set this as the minimum to be safe.
|
|
25
|
+
# For more information: https://github.com/pytorch/pytorch/pull/136942
|
|
26
|
+
#
|
|
27
|
+
# Without AVX-512 (including aarch64), torch._int_mm uses a scalar fallback
|
|
28
|
+
# that is much slower than fp32 matmul. Only use it when AVX-512 is available.
|
|
29
|
+
if torch.__version__ >= (2, 6) and _has_avx512:
|
|
30
|
+
|
|
31
|
+
@register_kernel("bitsandbytes::int8_linear_matmul", "cpu")
|
|
32
|
+
def _(A: torch.Tensor, B: torch.Tensor):
|
|
33
|
+
return torch._int_mm(
|
|
34
|
+
A.reshape(-1, A.shape[-1]),
|
|
35
|
+
B.t(),
|
|
36
|
+
).reshape(*A.shape[:-1], B.shape[0])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if not isinstance(lib, ErrorHandlerMockBNBNativeLibrary):
|
|
40
|
+
|
|
41
|
+
@register_kernel("bitsandbytes::quantize_blockwise", "cpu")
|
|
42
|
+
def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
|
|
43
|
+
A = A.contiguous()
|
|
44
|
+
n = A.numel()
|
|
45
|
+
blocks = -(n // -blocksize)
|
|
46
|
+
|
|
47
|
+
absmax = torch.empty((blocks,), device=A.device, dtype=torch.float32)
|
|
48
|
+
out = torch.empty(A.shape, device=A.device, dtype=torch.uint8)
|
|
49
|
+
|
|
50
|
+
if A.dtype == torch.float32:
|
|
51
|
+
lib.cquantize_blockwise_cpu_fp32(
|
|
52
|
+
get_ptr(code),
|
|
53
|
+
get_ptr(A),
|
|
54
|
+
get_ptr(absmax),
|
|
55
|
+
get_ptr(out),
|
|
56
|
+
ct.c_longlong(blocksize),
|
|
57
|
+
ct.c_longlong(n),
|
|
58
|
+
)
|
|
59
|
+
elif A.dtype == torch.bfloat16:
|
|
60
|
+
lib.cquantize_blockwise_cpu_bf16(
|
|
61
|
+
get_ptr(code),
|
|
62
|
+
get_ptr(A),
|
|
63
|
+
get_ptr(absmax),
|
|
64
|
+
get_ptr(out),
|
|
65
|
+
ct.c_longlong(blocksize),
|
|
66
|
+
ct.c_longlong(n),
|
|
67
|
+
)
|
|
68
|
+
elif A.dtype == torch.float16:
|
|
69
|
+
lib.cquantize_blockwise_cpu_fp16(
|
|
70
|
+
get_ptr(code),
|
|
71
|
+
get_ptr(A),
|
|
72
|
+
get_ptr(absmax),
|
|
73
|
+
get_ptr(out),
|
|
74
|
+
ct.c_longlong(blocksize),
|
|
75
|
+
ct.c_longlong(n),
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
# Generic fallback for other dtypes
|
|
79
|
+
A_flat = A.reshape(n).float()
|
|
80
|
+
rem = n % blocksize
|
|
81
|
+
has_rem = rem > 0
|
|
82
|
+
A_com = A_flat[: n - rem]
|
|
83
|
+
A_com_reshaped = A_com.reshape(n // blocksize, blocksize)
|
|
84
|
+
absmax[: blocks - has_rem] = torch.abs(A_com_reshaped).max(dim=-1)[0]
|
|
85
|
+
scaled_A = torch.clamp(A_com_reshaped * (1 / absmax[: blocks - has_rem].view(-1, 1)), -1, 1)
|
|
86
|
+
scaled_A = scaled_A.reshape(-1)
|
|
87
|
+
if has_rem:
|
|
88
|
+
absmax[-1] = torch.abs(A_flat[n - rem :]).max()
|
|
89
|
+
scaled_A_rem = torch.clamp(A_flat[n - rem :] * (1 / absmax[-1]), -1, 1)
|
|
90
|
+
scaled_A = torch.cat([scaled_A, scaled_A_rem], dim=0)
|
|
91
|
+
|
|
92
|
+
diff = torch.abs(scaled_A.unsqueeze(-1) - code.to(scaled_A.device))
|
|
93
|
+
out = torch.argmin(diff, dim=-1).to(torch.uint8).to(scaled_A.device).reshape(A.shape)
|
|
94
|
+
|
|
95
|
+
return out, absmax
|
|
96
|
+
|
|
97
|
+
@register_kernel("bitsandbytes::dequantize_blockwise", "cpu")
|
|
98
|
+
def _(
|
|
99
|
+
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype
|
|
100
|
+
) -> torch.Tensor:
|
|
101
|
+
A = A.contiguous()
|
|
102
|
+
out = torch.empty_like(A, dtype=dtype)
|
|
103
|
+
if dtype == torch.float32:
|
|
104
|
+
lib.cdequantize_blockwise_cpu_fp32(
|
|
105
|
+
get_ptr(code),
|
|
106
|
+
get_ptr(A),
|
|
107
|
+
get_ptr(absmax),
|
|
108
|
+
get_ptr(out),
|
|
109
|
+
ct.c_longlong(blocksize),
|
|
110
|
+
ct.c_longlong(A.numel()),
|
|
111
|
+
)
|
|
112
|
+
elif dtype == torch.bfloat16:
|
|
113
|
+
lib.cdequantize_blockwise_cpu_bf16(
|
|
114
|
+
get_ptr(code),
|
|
115
|
+
get_ptr(A),
|
|
116
|
+
get_ptr(absmax),
|
|
117
|
+
get_ptr(out),
|
|
118
|
+
ct.c_longlong(blocksize),
|
|
119
|
+
ct.c_longlong(A.numel()),
|
|
120
|
+
)
|
|
121
|
+
elif dtype == torch.float16:
|
|
122
|
+
lib.cdequantize_blockwise_cpu_fp16(
|
|
123
|
+
get_ptr(code),
|
|
124
|
+
get_ptr(A),
|
|
125
|
+
get_ptr(absmax),
|
|
126
|
+
get_ptr(out),
|
|
127
|
+
ct.c_longlong(blocksize),
|
|
128
|
+
ct.c_longlong(A.numel()),
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
out = code[A.reshape(-1).int()]
|
|
132
|
+
blocks = out.shape[-1] // blocksize
|
|
133
|
+
res = out.shape[-1] % blocksize
|
|
134
|
+
if res != 0:
|
|
135
|
+
out = torch.nn.functional.pad(out, (0, blocksize - res), mode="constant", value=0)
|
|
136
|
+
out = (out.view(-1, blocksize) * absmax.view(-1, 1)).to(dtype).reshape(-1)
|
|
137
|
+
out = out[: blocks * blocksize + res]
|
|
138
|
+
out = out.reshape(A.shape)
|
|
139
|
+
|
|
140
|
+
return out
|
|
141
|
+
|
|
142
|
+
@register_kernel("bitsandbytes::dequantize_4bit", "cpu")
|
|
143
|
+
def _(
|
|
144
|
+
A: torch.Tensor,
|
|
145
|
+
absmax: torch.Tensor,
|
|
146
|
+
blocksize: int,
|
|
147
|
+
quant_type: str,
|
|
148
|
+
shape: Sequence[int],
|
|
149
|
+
dtype: torch.dtype,
|
|
150
|
+
) -> torch.Tensor:
|
|
151
|
+
# Fallback as AVX512 implementation has accuracy issues with blocksize >= 2048.
|
|
152
|
+
# Note: this is not a common use case.
|
|
153
|
+
avx512_fallback = _has_avx512 and blocksize >= 2048
|
|
154
|
+
|
|
155
|
+
# Odd shape is not supported by this kernel; fallback to generic implementation
|
|
156
|
+
shape_fallback = shape[-1] % 2 != 0
|
|
157
|
+
|
|
158
|
+
if avx512_fallback or shape_fallback:
|
|
159
|
+
from ..default.ops import _dequantize_4bit_compute
|
|
160
|
+
from ..utils import _get_4bit_code
|
|
161
|
+
|
|
162
|
+
if A.dtype != torch.uint8:
|
|
163
|
+
A = A.view(torch.uint8)
|
|
164
|
+
code = _get_4bit_code(quant_type, A.device)
|
|
165
|
+
return _dequantize_4bit_compute(A.reshape(-1), absmax, code, blocksize, shape, dtype)
|
|
166
|
+
|
|
167
|
+
# Enable non uint8 dtype
|
|
168
|
+
if A.dtype != torch.uint8:
|
|
169
|
+
A = A.view(torch.uint8)
|
|
170
|
+
|
|
171
|
+
# TODO: support half precision absmax
|
|
172
|
+
if absmax.dtype != torch.float32:
|
|
173
|
+
absmax = absmax.float()
|
|
174
|
+
|
|
175
|
+
if len(shape) == 1:
|
|
176
|
+
shape = (1, shape[0])
|
|
177
|
+
|
|
178
|
+
m = prod(shape[:-1])
|
|
179
|
+
n = shape[-1]
|
|
180
|
+
|
|
181
|
+
A = A.reshape(m, n // 2)
|
|
182
|
+
out = torch.empty(shape, dtype=dtype, device=A.device)
|
|
183
|
+
|
|
184
|
+
if quant_type == "fp4":
|
|
185
|
+
if dtype == torch.float32:
|
|
186
|
+
lib.cdequantize_blockwise_cpu_fp4_fp32(
|
|
187
|
+
get_ptr(A),
|
|
188
|
+
get_ptr(absmax),
|
|
189
|
+
get_ptr(out),
|
|
190
|
+
ct.c_longlong(blocksize),
|
|
191
|
+
ct.c_longlong(m),
|
|
192
|
+
ct.c_longlong(n),
|
|
193
|
+
)
|
|
194
|
+
elif dtype == torch.bfloat16:
|
|
195
|
+
lib.cdequantize_blockwise_cpu_fp4_bf16(
|
|
196
|
+
get_ptr(A),
|
|
197
|
+
get_ptr(absmax),
|
|
198
|
+
get_ptr(out),
|
|
199
|
+
ct.c_longlong(blocksize),
|
|
200
|
+
ct.c_longlong(m),
|
|
201
|
+
ct.c_longlong(n),
|
|
202
|
+
)
|
|
203
|
+
elif dtype == torch.float16:
|
|
204
|
+
lib.cdequantize_blockwise_cpu_fp4_fp16(
|
|
205
|
+
get_ptr(A),
|
|
206
|
+
get_ptr(absmax),
|
|
207
|
+
get_ptr(out),
|
|
208
|
+
ct.c_longlong(blocksize),
|
|
209
|
+
ct.c_longlong(m),
|
|
210
|
+
ct.c_longlong(n),
|
|
211
|
+
)
|
|
212
|
+
elif quant_type == "nf4":
|
|
213
|
+
if dtype == torch.float32:
|
|
214
|
+
lib.cdequantize_blockwise_cpu_nf4_fp32(
|
|
215
|
+
get_ptr(A),
|
|
216
|
+
get_ptr(absmax),
|
|
217
|
+
get_ptr(out),
|
|
218
|
+
ct.c_longlong(blocksize),
|
|
219
|
+
ct.c_longlong(m),
|
|
220
|
+
ct.c_longlong(n),
|
|
221
|
+
)
|
|
222
|
+
elif dtype == torch.bfloat16:
|
|
223
|
+
lib.cdequantize_blockwise_cpu_nf4_bf16(
|
|
224
|
+
get_ptr(A),
|
|
225
|
+
get_ptr(absmax),
|
|
226
|
+
get_ptr(out),
|
|
227
|
+
ct.c_longlong(blocksize),
|
|
228
|
+
ct.c_longlong(m),
|
|
229
|
+
ct.c_longlong(n),
|
|
230
|
+
)
|
|
231
|
+
elif dtype == torch.float16:
|
|
232
|
+
lib.cdequantize_blockwise_cpu_nf4_fp16(
|
|
233
|
+
get_ptr(A),
|
|
234
|
+
get_ptr(absmax),
|
|
235
|
+
get_ptr(out),
|
|
236
|
+
ct.c_longlong(blocksize),
|
|
237
|
+
ct.c_longlong(m),
|
|
238
|
+
ct.c_longlong(n),
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
raise ValueError
|
|
242
|
+
|
|
243
|
+
return out
|
|
244
|
+
|
|
245
|
+
# NOTE 2026-09-22: the registration below used to live INSIDE `if has_avx512bf16():`,
|
|
246
|
+
# which meant AVX2-only CPUs (Zen2 4500U / Comet Lake i5 / EPYC Zen2-3) never
|
|
247
|
+
# registered a CPU kernel for gemv_4bit at all. They silently fell through to the
|
|
248
|
+
# default backend, which dequantizes the whole weight matrix and then runs a dense
|
|
249
|
+
# linear -- measured 12.3 ms per call vs 1.16 ms for the fused kernel below (10.6x).
|
|
250
|
+
# The guard is therefore narrowed to cover only the AVX512-BF16-only HuggingFace
|
|
251
|
+
# kernel load; the fused registration is unconditional.
|
|
252
|
+
gemm_4bit_forward_kernel = None
|
|
253
|
+
if has_avx512bf16():
|
|
254
|
+
gemm_4bit_forward_kernel = None
|
|
255
|
+
try:
|
|
256
|
+
from kernels import get_kernel
|
|
257
|
+
|
|
258
|
+
gemm_4bit_forward_kernel = get_kernel(
|
|
259
|
+
"kernels-community/quantization-bitsandbytes", version=1
|
|
260
|
+
).gemm_4bit_forward
|
|
261
|
+
except Exception as exc: # pragma: no cover - best effort fallback
|
|
262
|
+
gemm_4bit_forward_kernel = None
|
|
263
|
+
logger.warning(
|
|
264
|
+
"Failed to load CPU gemm_4bit_forward from kernels-community: %s. Please make sure you already `pip install kernels` and the kernels >= 0.11.1",
|
|
265
|
+
exc,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
@register_kernel("bitsandbytes::gemv_4bit", "cpu")
|
|
269
|
+
def _(
|
|
270
|
+
A: torch.Tensor,
|
|
271
|
+
B: torch.Tensor,
|
|
272
|
+
shapeB: Sequence[int],
|
|
273
|
+
absmax: torch.Tensor,
|
|
274
|
+
code: torch.Tensor,
|
|
275
|
+
blocksize: int,
|
|
276
|
+
) -> torch.Tensor:
|
|
277
|
+
if B.dtype != torch.uint8:
|
|
278
|
+
B = B.contiguous().view(torch.uint8)
|
|
279
|
+
dtype = A.dtype
|
|
280
|
+
A_orig = A
|
|
281
|
+
absmax_orig = absmax
|
|
282
|
+
quant_type = "fp4" if code[1] > 0 else "nf4"
|
|
283
|
+
# cpu fused op only support bf16 for now.
|
|
284
|
+
if dtype != torch.bfloat16:
|
|
285
|
+
A = A.to(torch.bfloat16)
|
|
286
|
+
if absmax.dtype != torch.bfloat16:
|
|
287
|
+
absmax = absmax.to(torch.bfloat16)
|
|
288
|
+
|
|
289
|
+
final_out_shape = (*A.shape[:-1], shapeB[0])
|
|
290
|
+
A = A.reshape(-1, A.shape[-1])
|
|
291
|
+
out_shape = (*A.shape[:-1], shapeB[0])
|
|
292
|
+
if gemm_4bit_forward_kernel is not None:
|
|
293
|
+
quant_type_num = 1 if quant_type == "fp4" else 0
|
|
294
|
+
# C++ kernel expects weight shape (N, K_packed), ensure 2D contiguous
|
|
295
|
+
B_2d = B.reshape(shapeB[0], -1).contiguous()
|
|
296
|
+
out = gemm_4bit_forward_kernel(A, B_2d, absmax, blocksize, quant_type_num)
|
|
297
|
+
else:
|
|
298
|
+
# AVX2 回退:用所有 CPU 构建都导出的 cgemv_4bit_inference_cpu_* 融合内核
|
|
299
|
+
# (lib.gemv_4bit_inference_cpu_fp4/nf4_bf16 只存在于 AVX512+BF16 构建,
|
|
300
|
+
# AVX2 机器走到会 AttributeError;data_type: FP4=1 / NF4=2)
|
|
301
|
+
A_c = A_orig.reshape(-1, A_orig.shape[-1]).contiguous()
|
|
302
|
+
B_c = B.reshape(shapeB[0], -1).contiguous()
|
|
303
|
+
absmax_c = absmax_orig.float().contiguous()
|
|
304
|
+
data_type_num = 1 if quant_type == "fp4" else 2
|
|
305
|
+
M, K = A_c.shape[0], A_c.shape[1]
|
|
306
|
+
N = shapeB[0]
|
|
307
|
+
out = torch.empty(out_shape, dtype=A_c.dtype, device=A_c.device)
|
|
308
|
+
if dtype == torch.float32:
|
|
309
|
+
lib.cgemv_4bit_inference_cpu_fp32(
|
|
310
|
+
get_ptr(A_c), get_ptr(B_c), get_ptr(absmax_c), get_ptr(out),
|
|
311
|
+
ct.c_longlong(M), ct.c_longlong(N), ct.c_longlong(K),
|
|
312
|
+
ct.c_longlong(A_c.stride(0)), ct.c_longlong(K // 2),
|
|
313
|
+
ct.c_longlong(out.stride(0)), ct.c_longlong(blocksize),
|
|
314
|
+
ct.c_int(data_type_num),
|
|
315
|
+
)
|
|
316
|
+
elif dtype == torch.bfloat16:
|
|
317
|
+
lib.cgemv_4bit_inference_cpu_bf16(
|
|
318
|
+
get_ptr(A_c), get_ptr(B_c), get_ptr(absmax_c), get_ptr(out),
|
|
319
|
+
ct.c_longlong(M), ct.c_longlong(N), ct.c_longlong(K),
|
|
320
|
+
ct.c_longlong(A_c.stride(0)), ct.c_longlong(K // 2),
|
|
321
|
+
ct.c_longlong(out.stride(0)), ct.c_longlong(blocksize),
|
|
322
|
+
ct.c_int(data_type_num),
|
|
323
|
+
)
|
|
324
|
+
else:
|
|
325
|
+
lib.cgemv_4bit_inference_cpu_fp16(
|
|
326
|
+
get_ptr(A_c), get_ptr(B_c), get_ptr(absmax_c), get_ptr(out),
|
|
327
|
+
ct.c_longlong(M), ct.c_longlong(N), ct.c_longlong(K),
|
|
328
|
+
ct.c_longlong(A_c.stride(0)), ct.c_longlong(K // 2),
|
|
329
|
+
ct.c_longlong(out.stride(0)), ct.c_longlong(blocksize),
|
|
330
|
+
ct.c_int(data_type_num),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
if dtype != torch.bfloat16:
|
|
334
|
+
out = out.to(dtype)
|
|
335
|
+
|
|
336
|
+
return out.reshape(final_out_shape)
|
|
337
|
+
|
|
338
|
+
# ---- gemm_4bit: CPU 注册(2026-09-22 新增)----
|
|
339
|
+
# 此前这个 op 【没有 cpu 注册】,于是退到 default 后端做"反量化 + 稠密线性"。
|
|
340
|
+
# autograd/_functions.py:324/334/462/472 调的就是它 ⇒ 真实模型里
|
|
341
|
+
# 【每一次批量 4-bit 线性前向】都在走慢路径。实测(K=N=4096):
|
|
342
|
+
# M=1 gemm_4bit 14.483 ms vs 融合内核 1.448 ms ⇒ 10.00x
|
|
343
|
+
# M=8 gemm_4bit 32.494 ms vs 融合内核 4.124 ms ⇒ 7.88x
|
|
344
|
+
# M=64 gemm_4bit 38.576 ms vs 融合内核 30.382 ms ⇒ 1.27x
|
|
345
|
+
# M=256 gemm_4bit 75.532 ms vs 融合内核 164.840 ms ⇒ 0.46x ← 融合反而慢
|
|
346
|
+
# 融合内核的 m 循环每 4 行重读一遍权重,所以大 M 时它输给 oneDNN 稠密。
|
|
347
|
+
# ⇒ 按 M 分档:小 M 走融合内核,大 M 交回 default。
|
|
348
|
+
# ---- 2026-09-22 实测三方交叉点(K=N=4096,OMP=6)----
|
|
349
|
+
# M 融合 ms default ms 融合优势
|
|
350
|
+
# 1 1.20 ~14.5 12x
|
|
351
|
+
# 8 4.71 25.14 5.3x
|
|
352
|
+
# 16 7.04 25.75 3.7x
|
|
353
|
+
# 17 7.79 26.43 3.4x
|
|
354
|
+
# 32 16.20 28.41 1.75x
|
|
355
|
+
# 64 28.79 31.37 1.09x
|
|
356
|
+
# 96 43.45 37.01 default 反超
|
|
357
|
+
# 128 62.11 41.77 default 快 1.49x
|
|
358
|
+
# 192 85.78 54.67 default 快 1.57x
|
|
359
|
+
# 256 166.11 64.94 default 快 2.56x
|
|
360
|
+
# ⇒ 融合内核一直赢到 M=64,default 要到 M≈96 才反超。
|
|
361
|
+
# 阈值原先写 16 ⇒ M=17~64 被错误地分给 default,白慢最多 3.4x。
|
|
362
|
+
#
|
|
363
|
+
# ⚠️ 一个被实测否决的尝试:给 C 层加"权重行解码一次、对全部 M 行复用"的路径,
|
|
364
|
+
# 结果是【负优化】(M=17 慢 1.52x、M=192 慢 1.44x)——
|
|
365
|
+
# 它把权重解码从 O(M/4) 降到 O(1),却让 A 被每个输出列重读一次,
|
|
366
|
+
# A 流量变成 O(N*M*K)(M=17 时 1.14 GB),远比重新解码权重贵。
|
|
367
|
+
# 已删除,只在 cpu_ops.cpp 里留了说明。
|
|
368
|
+
_GEMM_4BIT_FUSED_MAX_M = 64
|
|
369
|
+
|
|
370
|
+
@register_kernel("bitsandbytes::gemm_4bit", "cpu")
|
|
371
|
+
def _gemm_4bit_cpu(
|
|
372
|
+
A: torch.Tensor,
|
|
373
|
+
B: torch.Tensor,
|
|
374
|
+
shapeB: Sequence[int],
|
|
375
|
+
absmax: torch.Tensor,
|
|
376
|
+
blocksize: int,
|
|
377
|
+
quant_type: str,
|
|
378
|
+
bias: Optional[torch.Tensor] = None,
|
|
379
|
+
absmax_8bit: Optional[torch.Tensor] = None,
|
|
380
|
+
absmax_code: Optional[torch.Tensor] = None,
|
|
381
|
+
absmax_offset: Optional[torch.Tensor] = None,
|
|
382
|
+
) -> torch.Tensor:
|
|
383
|
+
m_rows = A.numel() // A.shape[-1] if A.numel() else 0
|
|
384
|
+
# 融合路径的限制:无 bias、非 nested absmax、张量是 2D 可 reshape 的、
|
|
385
|
+
# 且 M 足够小(大 M 时 oneDNN 稠密更快)。
|
|
386
|
+
can_fuse = (
|
|
387
|
+
gemm_4bit_forward_kernel is None # 没有 HF kernel 时才用我们的融合内核
|
|
388
|
+
and bias is None
|
|
389
|
+
and absmax_8bit is None
|
|
390
|
+
and m_rows > 0
|
|
391
|
+
and m_rows <= _GEMM_4BIT_FUSED_MAX_M
|
|
392
|
+
)
|
|
393
|
+
if can_fuse:
|
|
394
|
+
dtype = A.dtype
|
|
395
|
+
A_c = A.reshape(-1, A.shape[-1]).contiguous()
|
|
396
|
+
B_c = B.reshape(shapeB[0], -1)
|
|
397
|
+
if B_c.dtype != torch.uint8:
|
|
398
|
+
B_c = B_c.contiguous().view(torch.uint8)
|
|
399
|
+
else:
|
|
400
|
+
B_c = B_c.contiguous()
|
|
401
|
+
absmax_c = absmax.float().contiguous()
|
|
402
|
+
M, K = A_c.shape[0], A_c.shape[1]
|
|
403
|
+
N = shapeB[0]
|
|
404
|
+
data_type_num = 1 if quant_type == "fp4" else 2
|
|
405
|
+
out = torch.empty((M, N), dtype=dtype, device=A_c.device)
|
|
406
|
+
fn = {
|
|
407
|
+
torch.float32: lib.cgemv_4bit_inference_cpu_fp32,
|
|
408
|
+
torch.bfloat16: lib.cgemv_4bit_inference_cpu_bf16,
|
|
409
|
+
torch.float16: lib.cgemv_4bit_inference_cpu_fp16,
|
|
410
|
+
}[dtype]
|
|
411
|
+
fn(
|
|
412
|
+
get_ptr(A_c), get_ptr(B_c), get_ptr(absmax_c), get_ptr(out),
|
|
413
|
+
ct.c_longlong(M), ct.c_longlong(N), ct.c_longlong(K),
|
|
414
|
+
ct.c_longlong(A_c.stride(0)), ct.c_longlong(K // 2),
|
|
415
|
+
ct.c_longlong(out.stride(0)), ct.c_longlong(blocksize),
|
|
416
|
+
ct.c_int(data_type_num),
|
|
417
|
+
)
|
|
418
|
+
return out.reshape(*A.shape[:-1], N)
|
|
419
|
+
|
|
420
|
+
from ..default.ops import _gemm_4bit_default_impl
|
|
421
|
+
|
|
422
|
+
return _gemm_4bit_default_impl(
|
|
423
|
+
A, B, shapeB, absmax, blocksize, quant_type,
|
|
424
|
+
bias, absmax_8bit, absmax_code, absmax_offset,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# ---- 大 M 分档(2026-09-23 实测)----
|
|
428
|
+
# 融合内核的内层是 m0 += 4,每 4 行就把整块权重重新读一遍 ⇒ 权重流量 O(M/4),
|
|
429
|
+
# 大 M 时输给"反量化 + oneDNN 稠密 matmul"。
|
|
430
|
+
# 干净基准(gemm8_test.py:每格子进程 + 取 7 次最小值),融合/两步 的比值:
|
|
431
|
+
# M=16 0.18x / 0.15x / 0.25x 融合远优
|
|
432
|
+
# M=64 0.89x / 0.65x / 0.77x 融合仍优
|
|
433
|
+
# M=128 1.34x / 1.14x / 1.32x ← 交叉点在这一带
|
|
434
|
+
# M=256 1.54x / 1.34x / 1.51x
|
|
435
|
+
# M=512 2.04x / 1.82x / 2.04x 两步快约一倍
|
|
436
|
+
# ⇒ 阈值取 64(保守:M=64 时融合还是赢的,且真实推理的 M 是 seq_len)。
|
|
437
|
+
#
|
|
438
|
+
# ⚠️ 这个 op 【没有 default 后端回退】(全库只注册了 cpu 与 cuda),
|
|
439
|
+
# 所以大 M 路径必须在这里自己实现。8-bit 的码表是 create_dynamic_map(),
|
|
440
|
+
# 与 quantize_blockwise 默认用的同一个 ⇒ 数值一致。
|
|
441
|
+
_GEMM_8BIT_FUSED_MAX_M = 64
|
|
442
|
+
|
|
443
|
+
@register_kernel("bitsandbytes::gemm_8bit", "cpu")
|
|
444
|
+
def _gemm_8bit_cpu(
|
|
445
|
+
A: torch.Tensor,
|
|
446
|
+
B: torch.Tensor,
|
|
447
|
+
absmax: torch.Tensor,
|
|
448
|
+
blocksize: int,
|
|
449
|
+
) -> torch.Tensor:
|
|
450
|
+
"""融合 8bit 反量化 GEMM:out = A @ dequant8(B)(B 为 uint8 码流,逐行块量化)。
|
|
451
|
+
|
|
452
|
+
权重在整个乘法中保持 uint8(DRAM 流量为 fp32 的 1/4),无 fp32 临时张量。
|
|
453
|
+
小 M(<= 64)走融合内核;大 M 退到"反量化 + 稠密 matmul"(见上面的分档说明)。
|
|
454
|
+
"""
|
|
455
|
+
if A.numel() == 0:
|
|
456
|
+
return torch.empty((*A.shape[:-1], B.shape[0]), dtype=torch.float32, device=A.device)
|
|
457
|
+
A_f = A.reshape(-1, A.shape[-1]).contiguous()
|
|
458
|
+
B_c = B.contiguous()
|
|
459
|
+
absmax_c = absmax.contiguous().float()
|
|
460
|
+
M, K = A_f.shape
|
|
461
|
+
N = B_c.shape[0]
|
|
462
|
+
|
|
463
|
+
if M > _GEMM_8BIT_FUSED_MAX_M:
|
|
464
|
+
# 大 M:两步更快(实测 M=512 时 1.8~2.0x)
|
|
465
|
+
from bitsandbytes.functional import create_dynamic_map, dequantize_blockwise
|
|
466
|
+
|
|
467
|
+
code = create_dynamic_map().to(B_c.device)
|
|
468
|
+
W = dequantize_blockwise(B_c, absmax=absmax_c, code=code, blocksize=blocksize)
|
|
469
|
+
return (A_f @ W.t()).reshape(*A.shape[:-1], N)
|
|
470
|
+
|
|
471
|
+
out = torch.empty((M, N), dtype=torch.float32, device=A_f.device)
|
|
472
|
+
lib.cgemm_8bit_inference_cpu_fp32(
|
|
473
|
+
get_ptr(A_f),
|
|
474
|
+
get_ptr(B_c),
|
|
475
|
+
get_ptr(absmax_c),
|
|
476
|
+
get_ptr(out),
|
|
477
|
+
ct.c_longlong(M),
|
|
478
|
+
ct.c_longlong(N),
|
|
479
|
+
ct.c_longlong(K),
|
|
480
|
+
ct.c_longlong(A_f.stride(0)),
|
|
481
|
+
ct.c_longlong(B_c.stride(0)),
|
|
482
|
+
ct.c_longlong(out.stride(0)),
|
|
483
|
+
ct.c_longlong(blocksize),
|
|
484
|
+
)
|
|
485
|
+
return out.reshape(*A.shape[:-1], N)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ==================== CPU Optimizer Kernels ====================
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _compute_update_norm_and_scale(
|
|
492
|
+
update: torch.Tensor,
|
|
493
|
+
unorm_vec: Optional[torch.Tensor],
|
|
494
|
+
max_unorm: float,
|
|
495
|
+
param_norm: float,
|
|
496
|
+
) -> float:
|
|
497
|
+
"""Compute trust-ratio scaling factor for LAMB/LARS and store update norm."""
|
|
498
|
+
if max_unorm <= 0.0:
|
|
499
|
+
return 1.0
|
|
500
|
+
unorm = torch.norm(update).item()
|
|
501
|
+
if unorm_vec is not None:
|
|
502
|
+
unorm_vec.fill_(unorm)
|
|
503
|
+
if unorm > max_unorm * param_norm:
|
|
504
|
+
return (max_unorm * param_norm) / unorm
|
|
505
|
+
return 1.0
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@torch.no_grad()
|
|
509
|
+
def _optimizer_update_32bit_cpu(
|
|
510
|
+
optimizer_name: str,
|
|
511
|
+
g: torch.Tensor,
|
|
512
|
+
p: torch.Tensor,
|
|
513
|
+
state1: torch.Tensor,
|
|
514
|
+
state2: Optional[torch.Tensor],
|
|
515
|
+
unorm_vec: Optional[torch.Tensor],
|
|
516
|
+
max_unorm: float,
|
|
517
|
+
param_norm: float,
|
|
518
|
+
beta1: float,
|
|
519
|
+
beta2: float,
|
|
520
|
+
beta3: float,
|
|
521
|
+
alpha: float,
|
|
522
|
+
eps: float,
|
|
523
|
+
weight_decay: float,
|
|
524
|
+
step: int,
|
|
525
|
+
lr: float,
|
|
526
|
+
gnorm_scale: float,
|
|
527
|
+
skip_zeros: bool = False,
|
|
528
|
+
) -> None:
|
|
529
|
+
g_float = g.float() * gnorm_scale
|
|
530
|
+
p_float = p.data.float()
|
|
531
|
+
|
|
532
|
+
if optimizer_name in ("adam", "lamb"):
|
|
533
|
+
# Adam / LAMB (2-state): m and v
|
|
534
|
+
state1.mul_(beta1).add_(g_float, alpha=1.0 - beta1)
|
|
535
|
+
state2.mul_(beta2).addcmul_(g_float, g_float, value=1.0 - beta2)
|
|
536
|
+
|
|
537
|
+
correction1 = 1.0 - beta1**step
|
|
538
|
+
correction2 = math.sqrt(1.0 - beta2**step)
|
|
539
|
+
step_size = -lr * correction2 / correction1
|
|
540
|
+
|
|
541
|
+
if weight_decay > 0.0:
|
|
542
|
+
p_float.mul_(1.0 - lr * weight_decay)
|
|
543
|
+
|
|
544
|
+
update = state1 / (state2.sqrt() + eps * correction2)
|
|
545
|
+
|
|
546
|
+
update_scale = _compute_update_norm_and_scale(update, unorm_vec, max_unorm, param_norm)
|
|
547
|
+
p_float.add_(update, alpha=step_size * update_scale)
|
|
548
|
+
|
|
549
|
+
elif optimizer_name == "ademamix":
|
|
550
|
+
# AdEMAMix (2-state): state1 shape is (2, *p.shape), state1[0]=m1, state1[1]=m2
|
|
551
|
+
m1 = state1[0]
|
|
552
|
+
m2 = state1[1]
|
|
553
|
+
nu = state2
|
|
554
|
+
|
|
555
|
+
m1.mul_(beta1).add_(g_float, alpha=1.0 - beta1)
|
|
556
|
+
m2.mul_(beta3).add_(g_float, alpha=1.0 - beta3)
|
|
557
|
+
nu.mul_(beta2).addcmul_(g_float, g_float, value=1.0 - beta2)
|
|
558
|
+
|
|
559
|
+
correction1 = 1.0 - beta1**step
|
|
560
|
+
correction2 = math.sqrt(1.0 - beta2**step)
|
|
561
|
+
|
|
562
|
+
if weight_decay > 0.0:
|
|
563
|
+
p_float.mul_(1.0 - lr * weight_decay)
|
|
564
|
+
|
|
565
|
+
mixed_momentum = (m1 / correction1) + (alpha * m2)
|
|
566
|
+
adaptive_term = (nu.sqrt() / correction2) + eps
|
|
567
|
+
p_float.add_(mixed_momentum / adaptive_term, alpha=-lr)
|
|
568
|
+
|
|
569
|
+
elif optimizer_name in ("momentum", "lars"):
|
|
570
|
+
# SGD with momentum / LARS (1-state)
|
|
571
|
+
g_wd = g_float.add(p_float, alpha=weight_decay) if weight_decay > 0.0 else g_float
|
|
572
|
+
|
|
573
|
+
if step == 1:
|
|
574
|
+
state1.copy_(g_wd)
|
|
575
|
+
else:
|
|
576
|
+
state1.mul_(beta1).add_(g_wd)
|
|
577
|
+
|
|
578
|
+
update_scale = _compute_update_norm_and_scale(state1, unorm_vec, max_unorm, param_norm)
|
|
579
|
+
p_float.add_(state1, alpha=-lr * update_scale)
|
|
580
|
+
|
|
581
|
+
elif optimizer_name == "lion":
|
|
582
|
+
# Lion (2-state sign update)
|
|
583
|
+
if weight_decay > 0.0:
|
|
584
|
+
p_float.mul_(1.0 - lr * weight_decay)
|
|
585
|
+
|
|
586
|
+
update = state1.mul(beta1).add(g_float, alpha=1.0 - beta1)
|
|
587
|
+
p_float.add_(update.sign(), alpha=-lr)
|
|
588
|
+
|
|
589
|
+
state1.mul_(beta2).add_(g_float, alpha=1.0 - beta2)
|
|
590
|
+
|
|
591
|
+
elif optimizer_name == "rmsprop":
|
|
592
|
+
# RMSprop (1-state)
|
|
593
|
+
g_wd = g_float.add(p_float, alpha=weight_decay) if weight_decay > 0.0 else g_float
|
|
594
|
+
state1.mul_(beta1).addcmul_(g_wd, g_wd, value=1.0 - beta1)
|
|
595
|
+
|
|
596
|
+
update = g_wd / (state1.sqrt() + eps)
|
|
597
|
+
update_scale = _compute_update_norm_and_scale(update, unorm_vec, max_unorm, param_norm)
|
|
598
|
+
p_float.add_(update, alpha=-lr * update_scale)
|
|
599
|
+
|
|
600
|
+
elif optimizer_name == "adagrad":
|
|
601
|
+
# Adagrad (1-state)
|
|
602
|
+
g_wd = g_float.add(p_float, alpha=weight_decay) if weight_decay > 0.0 else g_float
|
|
603
|
+
state1.addcmul_(g_wd, g_wd, value=1.0)
|
|
604
|
+
|
|
605
|
+
update = g_wd / (state1.sqrt() + eps)
|
|
606
|
+
p_float.add_(update, alpha=-lr)
|
|
607
|
+
|
|
608
|
+
else:
|
|
609
|
+
raise ValueError(f"Unsupported optimizer for CPU: {optimizer_name}")
|
|
610
|
+
|
|
611
|
+
# Write back to original precision
|
|
612
|
+
p.data.copy_(p_float)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
register_kernel("bitsandbytes::optimizer_update_32bit", "cpu")(_optimizer_update_32bit_cpu)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
@torch.no_grad()
|
|
619
|
+
def _dequant_blockwise_fp32_direct(
|
|
620
|
+
A_uint8: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int
|
|
621
|
+
) -> torch.Tensor:
|
|
622
|
+
return torch.ops.bitsandbytes.dequantize_blockwise(A_uint8, absmax, code, blocksize, torch.float32)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _quant_blockwise_fp32_direct(
|
|
626
|
+
A_fp32: torch.Tensor, code: torch.Tensor, absmax_out: torch.Tensor, out_uint8: torch.Tensor, blocksize: int
|
|
627
|
+
) -> None:
|
|
628
|
+
out, absmax = torch.ops.bitsandbytes.quantize_blockwise(A_fp32, code, blocksize)
|
|
629
|
+
out_uint8.copy_(out)
|
|
630
|
+
absmax_out.copy_(absmax)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _optimizer_update_8bit_blockwise_cpu(
|
|
634
|
+
optimizer_name: str,
|
|
635
|
+
g: torch.Tensor,
|
|
636
|
+
p: torch.Tensor,
|
|
637
|
+
state1: torch.Tensor,
|
|
638
|
+
state2: Optional[torch.Tensor],
|
|
639
|
+
beta1: float,
|
|
640
|
+
beta2: float,
|
|
641
|
+
beta3: float,
|
|
642
|
+
alpha: float,
|
|
643
|
+
eps: float,
|
|
644
|
+
step: int,
|
|
645
|
+
lr: float,
|
|
646
|
+
qmap1: torch.Tensor,
|
|
647
|
+
qmap2: Optional[torch.Tensor],
|
|
648
|
+
absmax1: torch.Tensor,
|
|
649
|
+
absmax2: Optional[torch.Tensor],
|
|
650
|
+
weight_decay: float,
|
|
651
|
+
gnorm_scale: float,
|
|
652
|
+
skip_zeros: bool = False,
|
|
653
|
+
) -> None:
|
|
654
|
+
# Fused single-pass C kernel (dequant + update + p update + requant in one
|
|
655
|
+
# sweep). Falls back to the composite PyTorch path below when the native
|
|
656
|
+
# library is unavailable or the optimizer is not supported by the kernel.
|
|
657
|
+
_OPT_IDS = {"adam": 0, "lamb": 0, "momentum": 1, "lars": 1, "lion": 2, "rmsprop": 3, "adagrad": 4, "ademamix": 5}
|
|
658
|
+
n = p.numel()
|
|
659
|
+
# The C kernel walks raw pointers and assumes C-contiguous layouts: a
|
|
660
|
+
# non-contiguous grad (transposed / sliced views are common in training)
|
|
661
|
+
# would silently read the wrong elements and corrupt the states.
|
|
662
|
+
contiguous_layout = (
|
|
663
|
+
g.is_contiguous()
|
|
664
|
+
and p.is_contiguous()
|
|
665
|
+
and state1.is_contiguous()
|
|
666
|
+
and (state2 is None or state2.is_contiguous())
|
|
667
|
+
and qmap1.is_contiguous()
|
|
668
|
+
and (qmap2 is None or qmap2.is_contiguous())
|
|
669
|
+
and absmax1.is_contiguous()
|
|
670
|
+
and (absmax2 is None or absmax2.is_contiguous())
|
|
671
|
+
)
|
|
672
|
+
if (
|
|
673
|
+
not isinstance(lib, ErrorHandlerMockBNBNativeLibrary)
|
|
674
|
+
and contiguous_layout
|
|
675
|
+
and optimizer_name in _OPT_IDS
|
|
676
|
+
and g.dtype in (torch.float32, torch.bfloat16, torch.float16)
|
|
677
|
+
# ademamix packs a second momentum into state1[n:2n] with its absmax in
|
|
678
|
+
# absmax1[blocks:2*blocks]; the flat C layout requires that packing.
|
|
679
|
+
and (optimizer_name != "ademamix" or state1.numel() >= 2 * n)
|
|
680
|
+
):
|
|
681
|
+
try:
|
|
682
|
+
dtype_id = {torch.float32: 0, torch.bfloat16: 1, torch.float16: 2}[g.dtype]
|
|
683
|
+
# Every scalar needs an explicit ctypes width: a bare Python int is
|
|
684
|
+
# marshalled as 32-bit c_int, but `n` is a long long. libffi writes
|
|
685
|
+
# only the low 4 bytes of its 8-byte stack slot; the C side reads
|
|
686
|
+
# all 8, so stale stack bits leak into n (observed: 0x7FFF00004000
|
|
687
|
+
# for n=16384) and the kernel walks out of bounds -> SIGSEGV.
|
|
688
|
+
lib.coptimizer_update_8bit_blockwise_cpu(
|
|
689
|
+
ct.c_int(_OPT_IDS[optimizer_name]),
|
|
690
|
+
get_ptr(g),
|
|
691
|
+
get_ptr(p),
|
|
692
|
+
get_ptr(state1),
|
|
693
|
+
get_ptr(state2) if state2 is not None else ct.c_void_p(0),
|
|
694
|
+
ct.c_float(beta1),
|
|
695
|
+
ct.c_float(beta2),
|
|
696
|
+
ct.c_float(beta3),
|
|
697
|
+
ct.c_float(alpha),
|
|
698
|
+
ct.c_float(eps),
|
|
699
|
+
ct.c_int(step),
|
|
700
|
+
ct.c_float(lr),
|
|
701
|
+
get_ptr(qmap1),
|
|
702
|
+
get_ptr(qmap2) if qmap2 is not None else ct.c_void_p(0),
|
|
703
|
+
get_ptr(absmax1),
|
|
704
|
+
get_ptr(absmax2) if absmax2 is not None else ct.c_void_p(0),
|
|
705
|
+
ct.c_float(weight_decay),
|
|
706
|
+
ct.c_float(gnorm_scale),
|
|
707
|
+
ct.c_int(1 if skip_zeros else 0),
|
|
708
|
+
ct.c_longlong(n),
|
|
709
|
+
ct.c_int(dtype_id),
|
|
710
|
+
)
|
|
711
|
+
return
|
|
712
|
+
except (AttributeError, RuntimeError):
|
|
713
|
+
pass # old native library without the fused entry point
|
|
714
|
+
|
|
715
|
+
blocksize = 256
|
|
716
|
+
|
|
717
|
+
# Dequantize states
|
|
718
|
+
if optimizer_name == "ademamix" and absmax1.ndim == 2:
|
|
719
|
+
s1_1 = _dequant_blockwise_fp32_direct(state1[0], absmax1[0], qmap1, blocksize)
|
|
720
|
+
s1_2 = _dequant_blockwise_fp32_direct(state1[1], absmax1[1], qmap1, blocksize)
|
|
721
|
+
state1_fp32 = torch.stack([s1_1, s1_2])
|
|
722
|
+
else:
|
|
723
|
+
state1_fp32 = _dequant_blockwise_fp32_direct(state1, absmax1, qmap1, blocksize)
|
|
724
|
+
|
|
725
|
+
state2_fp32 = None
|
|
726
|
+
if state2 is not None and qmap2 is not None and absmax2 is not None:
|
|
727
|
+
state2_fp32 = _dequant_blockwise_fp32_direct(state2, absmax2, qmap2, blocksize)
|
|
728
|
+
|
|
729
|
+
grad = g.float() * gnorm_scale
|
|
730
|
+
p_fp32 = p.data.float()
|
|
731
|
+
|
|
732
|
+
if optimizer_name in ("adam", "lamb"):
|
|
733
|
+
state1_fp32.mul_(beta1).add_(grad, alpha=1.0 - beta1)
|
|
734
|
+
state2_fp32.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
|
|
735
|
+
|
|
736
|
+
correction1 = 1.0 - beta1**step
|
|
737
|
+
correction2 = math.sqrt(1.0 - beta2**step)
|
|
738
|
+
|
|
739
|
+
denom = (state2_fp32.sqrt() / correction2).add_(eps)
|
|
740
|
+
if weight_decay > 0.0:
|
|
741
|
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
|
742
|
+
p_fp32.addcdiv_(state1_fp32, denom, value=-lr / correction1)
|
|
743
|
+
|
|
744
|
+
elif optimizer_name == "ademamix":
|
|
745
|
+
m1_fp32, m2_fp32 = state1_fp32[0], state1_fp32[1]
|
|
746
|
+
nu_fp32 = state2_fp32
|
|
747
|
+
|
|
748
|
+
m1_fp32.mul_(beta1).add_(grad, alpha=1.0 - beta1)
|
|
749
|
+
m2_fp32.mul_(beta3).add_(grad, alpha=1.0 - beta3)
|
|
750
|
+
nu_fp32.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
|
|
751
|
+
|
|
752
|
+
correction1 = 1.0 - beta1**step
|
|
753
|
+
correction2 = math.sqrt(1.0 - beta2**step)
|
|
754
|
+
|
|
755
|
+
update = (m1_fp32 / correction1 + alpha * m2_fp32) / (nu_fp32.sqrt() / correction2 + eps)
|
|
756
|
+
if weight_decay > 0.0:
|
|
757
|
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
|
758
|
+
p_fp32.add_(update, alpha=-lr)
|
|
759
|
+
|
|
760
|
+
state1_fp32 = torch.stack([m1_fp32, m2_fp32])
|
|
761
|
+
|
|
762
|
+
elif optimizer_name in ("momentum", "lars"):
|
|
763
|
+
grad.add_(p_fp32, alpha=weight_decay)
|
|
764
|
+
if step == 1:
|
|
765
|
+
state1_fp32.copy_(grad)
|
|
766
|
+
else:
|
|
767
|
+
state1_fp32.mul_(beta1).add_(grad)
|
|
768
|
+
p_fp32.add_(state1_fp32, alpha=-lr)
|
|
769
|
+
|
|
770
|
+
elif optimizer_name == "lion":
|
|
771
|
+
if weight_decay > 0.0:
|
|
772
|
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
|
773
|
+
|
|
774
|
+
update_dir = torch.sign(state1_fp32.mul(beta1) + grad.mul(1.0 - beta1))
|
|
775
|
+
p_fp32.add_(update_dir, alpha=-lr)
|
|
776
|
+
|
|
777
|
+
state1_fp32.mul_(beta2).add_(grad, alpha=1.0 - beta2)
|
|
778
|
+
|
|
779
|
+
elif optimizer_name == "rmsprop":
|
|
780
|
+
grad.add_(p_fp32, alpha=weight_decay)
|
|
781
|
+
state1_fp32.mul_(beta1).addcmul_(grad, grad, value=1.0 - beta1)
|
|
782
|
+
p_fp32.addcdiv_(grad, state1_fp32.sqrt().add_(eps), value=-lr)
|
|
783
|
+
|
|
784
|
+
elif optimizer_name == "adagrad":
|
|
785
|
+
grad.add_(p_fp32, alpha=weight_decay)
|
|
786
|
+
state1_fp32.addcmul_(grad, grad, value=1.0)
|
|
787
|
+
p_fp32.addcdiv_(grad, state1_fp32.sqrt().add_(eps), value=-lr)
|
|
788
|
+
|
|
789
|
+
else:
|
|
790
|
+
raise ValueError(f"Unsupported optimizer for CPU 8-bit: {optimizer_name}")
|
|
791
|
+
|
|
792
|
+
p.data.copy_(p_fp32)
|
|
793
|
+
|
|
794
|
+
# Re-quantize states
|
|
795
|
+
if optimizer_name == "ademamix":
|
|
796
|
+
_quant_blockwise_fp32_direct(state1_fp32[0], qmap1, absmax1[0], state1[0], blocksize)
|
|
797
|
+
_quant_blockwise_fp32_direct(state1_fp32[1], qmap1, absmax1[1], state1[1], blocksize)
|
|
798
|
+
_quant_blockwise_fp32_direct(state2_fp32, qmap2, absmax2, state2, blocksize)
|
|
799
|
+
else:
|
|
800
|
+
_quant_blockwise_fp32_direct(state1_fp32, qmap1, absmax1, state1, blocksize)
|
|
801
|
+
if state2_fp32 is not None:
|
|
802
|
+
_quant_blockwise_fp32_direct(state2_fp32, qmap2, absmax2, state2, blocksize)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
register_kernel("bitsandbytes::optimizer_update_8bit_blockwise", "cpu")(_optimizer_update_8bit_blockwise_cpu)
|