Skip to content

Commit ba40881

Browse files
Reduce Python CPU overhead and improve validation messages (#1953)
1 parent 857b83d commit ba40881

11 files changed

Lines changed: 368 additions & 335 deletions

File tree

bitsandbytes/_ops.py

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from collections.abc import Sequence
2-
from math import prod
32
from typing import Optional
43

54
import torch
@@ -96,7 +95,7 @@ def _(A: torch.Tensor, B: torch.Tensor, out: torch.Tensor):
9695
@register_fake("bitsandbytes::int8_vectorwise_quant")
9796
def _(A: torch.Tensor, threshold=0.0):
9897
out_row = torch.empty(A.shape, device=A.device, dtype=torch.int8)
99-
row_stats = torch.empty(prod(A.shape[:-1]), device=A.device, dtype=torch.float32)
98+
row_stats = torch.empty(A.numel() // A.shape[-1], device=A.device, dtype=torch.float32)
10099

101100
if threshold == 0.0:
102101
return out_row, row_stats, None
@@ -153,7 +152,7 @@ def _(
153152
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
154153
out_row = torch.empty_like(A, dtype=torch.int8)
155154
out_col = torch.empty_like(A, dtype=torch.int8)
156-
row_stats = torch.empty(prod(A.shape[:-1]), device=A.device, dtype=torch.float32)
155+
row_stats = torch.empty(A.numel() // A.shape[-1], device=A.device, dtype=torch.float32)
157156
col_stats = torch.empty(A.shape[-1], device=A.device, dtype=torch.float32)
158157
outlier_n = torch.library.get_ctx().new_dynamic_size()
159158
outlier_cols = A.new_empty(outlier_n, dtype=torch.int64)
@@ -175,7 +174,13 @@ def _(
175174
shape: Sequence[int],
176175
dtype: torch.dtype,
177176
) -> torch.Tensor:
178-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
177+
torch._check(blocksize in (32, 64, 128, 256, 512, 1024, 2048, 4096), lambda: f"invalid blocksize {blocksize}")
178+
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be 'nf4' or 'fp4', got {quant_type!r}")
179+
torch._check(absmax.dtype == torch.float32, lambda: f"absmax must be float32, got {absmax.dtype}")
180+
torch._check(
181+
dtype in (torch.float16, torch.bfloat16, torch.float32),
182+
lambda: f"Blockwise 4bit dequantization only supports 16/32-bit floats, but got {dtype}",
183+
)
179184
return torch.empty(shape, dtype=dtype, device=A.device)
180185

181186

@@ -195,7 +200,13 @@ def _(
195200
dtype: torch.dtype,
196201
out: torch.Tensor,
197202
) -> None:
198-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
203+
torch._check(blocksize in (32, 64, 128, 256, 512, 1024, 2048, 4096), lambda: f"invalid blocksize {blocksize}")
204+
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be 'nf4' or 'fp4', got {quant_type!r}")
205+
torch._check(absmax.dtype == torch.float32, lambda: f"absmax must be float32, got {absmax.dtype}")
206+
torch._check(
207+
dtype in (torch.float16, torch.bfloat16, torch.float32),
208+
lambda: f"Blockwise 4bit dequantization only supports 16/32-bit floats, but got {dtype}",
209+
)
199210
torch._check(out.shape == shape, lambda: f"Expected out.shape == {shape}, got {out.shape}")
200211
torch._check(out.device == A.device, lambda: f"Expected out.device == {A.device}, got {out.device}")
201212
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
@@ -211,7 +222,12 @@ def _(
211222
def _(
212223
A: torch.Tensor, blocksize: int, quant_type: str, quant_storage: torch.dtype
213224
) -> tuple[torch.Tensor, torch.Tensor]:
214-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
225+
torch._check(blocksize in (32, 64, 128, 256, 512, 1024, 2048, 4096), lambda: f"invalid blocksize {blocksize}")
226+
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be 'nf4' or 'fp4', got {quant_type!r}")
227+
torch._check(
228+
A.dtype in (torch.float16, torch.bfloat16, torch.float32),
229+
lambda: f"Blockwise 4bit quantization only supports 16/32-bit floats, but got {A.dtype}",
230+
)
215231

216232
n = A.numel()
217233
blocks = -(n // -blocksize)
@@ -250,7 +266,7 @@ def _(
250266
B.dtype in (torch.uint8, torch.bfloat16, torch.float16, torch.float32),
251267
lambda: f"B must be backed by storage of type uint8, bfloat16, float16, or float32, got {B.dtype}",
252268
)
253-
torch._check(blocksize in [32, 64, 128, 256, 512, 1024, 2048, 4096], lambda: f"invalid blocksize {blocksize}")
269+
torch._check(blocksize in (32, 64, 128, 256, 512, 1024, 2048, 4096), lambda: f"invalid blocksize {blocksize}")
254270
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be 'nf4' or 'fp4', got {quant_type!r}")
255271
torch._check(absmax.dtype == torch.float32, lambda: f"absmax must be float32, got {absmax.dtype}")
256272
if absmax_8bit is not None:
@@ -287,8 +303,12 @@ def _(
287303

288304
@register_fake("bitsandbytes::dequantize_blockwise")
289305
def _(A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype) -> torch.Tensor:
290-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
306+
torch._check(blocksize > 0, lambda: f"blocksize must be positive, got {blocksize}")
291307
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
308+
torch._check(
309+
dtype in (torch.float16, torch.bfloat16, torch.float32),
310+
lambda: f"Blockwise dequantization only supports 16/32-bit floats, but got {dtype}",
311+
)
292312
return torch.empty_like(A, dtype=dtype)
293313

294314

@@ -302,8 +322,12 @@ def _(A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int,
302322
def _(
303323
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype, out: torch.Tensor
304324
):
305-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
325+
torch._check(blocksize > 0, lambda: f"blocksize must be positive, got {blocksize}")
306326
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
327+
torch._check(
328+
dtype in (torch.float16, torch.bfloat16, torch.float32),
329+
lambda: f"Blockwise dequantization only supports 16/32-bit floats, but got {dtype}",
330+
)
307331
torch._check(out.shape == A.shape, lambda: f"Expected out.shape == {A.shape}, got {out.shape}")
308332
torch._check(out.device == A.device, lambda: f"Expected out.device == {A.device}, got {out.device}")
309333
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
@@ -314,7 +338,11 @@ def _(
314338

315339
@register_fake("bitsandbytes::quantize_blockwise")
316340
def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
317-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
341+
torch._check(blocksize > 0, lambda: f"blocksize must be positive, got {blocksize}")
342+
torch._check(
343+
A.dtype in (torch.float16, torch.bfloat16, torch.float32),
344+
lambda: f"Blockwise quantization only supports 16/32-bit floats, but got {A.dtype}",
345+
)
318346
n = A.numel()
319347
blocks = -(n // -blocksize)
320348
absmax = torch.empty((blocks,), device=A.device, dtype=torch.float32)
@@ -332,14 +360,13 @@ def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor
332360
def _(
333361
A: torch.Tensor, B: torch.Tensor, shapeB: Sequence[int], absmax: torch.Tensor, code: torch.Tensor, blocksize: int
334362
) -> torch.Tensor:
335-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
336-
torch._check(A.numel() == A.size(-1), lambda: f"A must be a vector with leading dimensions of 1, got {A.shape}")
363+
torch._check(blocksize in (32, 64, 128, 256, 512, 1024, 2048, 4096), lambda: f"invalid blocksize {blocksize}")
337364
torch._check(
338-
A.dtype in [torch.float16, torch.bfloat16, torch.float32],
365+
A.dtype in (torch.float16, torch.bfloat16, torch.float32),
339366
lambda: f"A must be float16, bfloat16, or float32, got {A.dtype}",
340367
)
341368
torch._check(
342-
B.dtype in [torch.uint8, torch.bfloat16, torch.float16, torch.float32],
369+
B.dtype in (torch.uint8, torch.bfloat16, torch.float16, torch.float32),
343370
lambda: f"B must be backed by storage of type uint8, bfloat16, float16, or float32, got {B.dtype}",
344371
)
345372
shape = (*A.shape[:-1], shapeB[0])
@@ -362,14 +389,13 @@ def _(
362389
blocksize: int,
363390
out: torch.Tensor,
364391
) -> None:
365-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
366-
torch._check(A.numel() == A.size(-1), lambda: f"A must be a vector with leading dimensions of 1, got {A.shape}")
392+
torch._check(blocksize in (32, 64, 128, 256, 512, 1024, 2048, 4096), lambda: f"invalid blocksize {blocksize}")
367393
torch._check(
368-
A.dtype in [torch.float16, torch.bfloat16, torch.float32],
394+
A.dtype in (torch.float16, torch.bfloat16, torch.float32),
369395
lambda: f"A must be float16, bfloat16, or float32, got {A.dtype}",
370396
)
371397
torch._check(
372-
B.dtype in [torch.uint8, torch.bfloat16, torch.float16, torch.float32],
398+
B.dtype in (torch.uint8, torch.bfloat16, torch.float16, torch.float32),
373399
lambda: f"B must be backed by storage of type uint8, bfloat16, float16, or float32, got {B.dtype}",
374400
)
375401
torch._check(

bitsandbytes/autograd/_functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class MatMul4Bit(torch.autograd.Function):
304304
def forward(ctx, A, B, out=None, bias=None, quant_state: Optional[F.QuantState] = None):
305305
# default of pytorch behavior if inputs are empty
306306
ctx.is_empty = False
307-
if prod(A.shape) == 0:
307+
if A.numel() == 0:
308308
ctx.is_empty = True
309309
ctx.A = A
310310
ctx.B = B

bitsandbytes/backends/cpu/ops.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ def _(A: torch.Tensor, B: torch.Tensor):
3535

3636
@register_kernel("bitsandbytes::quantize_blockwise", "cpu")
3737
def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
38-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
39-
4038
n = A.numel()
4139
blocks = -(n // -blocksize)
4240

@@ -94,9 +92,6 @@ def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor
9492
def _(
9593
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype
9694
) -> torch.Tensor:
97-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
98-
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
99-
10095
out = torch.empty_like(A, dtype=dtype)
10196
if dtype == torch.float32:
10297
lib.cdequantize_blockwise_cpu_fp32(
@@ -146,13 +141,6 @@ def _(
146141
shape: Sequence[int],
147142
dtype: torch.dtype,
148143
) -> torch.Tensor:
149-
torch._check(blocksize >= 0, lambda: f"Blocksize must be non-negative, got {blocksize}")
150-
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be nf4 or fp4, got {quant_type}")
151-
torch._check(
152-
dtype in [torch.bfloat16, torch.float16, torch.float32],
153-
lambda: f"Blockwise 4bit dequantization only supports 16/32-bit floats, but got {dtype}",
154-
)
155-
156144
# Fallback as AVX512 implementation has accuracy issues with blocksize >= 2048.
157145
# Note: this is not a common use case.
158146
avx512_fallback = _has_avx512 and blocksize >= 2048

0 commit comments

Comments
 (0)