Skip to content

Commit 0155ecc

Browse files
New 4bit GEMM kernels for inference
1 parent 2de5ec3 commit 0155ecc

23 files changed

Lines changed: 2766 additions & 115 deletions

‎CMakeLists.txt‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,29 @@ if(BUILD_CUDA)
234234

235235
list(APPEND SRC_FILES ${GPU_FILES})
236236

237+
# 4-bit GEMM SIMT and dispatch compile for all selected archs.
238+
# sm75/sm80+ MMA files only compile if those archs are selected.
239+
set(_cc_all ${COMPUTE_CAPABILITY} ${_LATEST_CAPABILITY})
240+
list(APPEND SRC_FILES csrc/gemm_4bit_simt.cu csrc/gemm_4bit.cu)
241+
242+
if(75 IN_LIST _cc_all)
243+
# Builds only on sm75
244+
list(APPEND SRC_FILES csrc/gemm_4bit_sm75.cu)
245+
add_compile_definitions(BNB_HAS_GEMM4BIT_SM75)
246+
endif()
247+
248+
set(_cc_sm80plus)
249+
foreach(_cc IN LISTS _cc_all)
250+
if(_cc GREATER_EQUAL 80)
251+
list(APPEND _cc_sm80plus ${_cc})
252+
endif()
253+
endforeach()
254+
if(_cc_sm80plus)
255+
# Builds only on sm80+
256+
list(APPEND SRC_FILES csrc/gemm_4bit_sm80.cu)
257+
add_compile_definitions(BNB_HAS_GEMM4BIT_SM80)
258+
endif()
259+
237260
string(APPEND BNB_OUTPUT_NAME "_cuda${CUDA_VERSION_SHORT}")
238261
add_compile_definitions(BUILD_CUDA)
239262
elseif(BUILD_HIP)

‎bitsandbytes/_ops.py‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,65 @@ def _(
228228
return out, absmax
229229

230230

231+
torch.library.define(
232+
"bitsandbytes::gemm_4bit",
233+
"(Tensor A, Tensor B, int[] shapeB, Tensor absmax, int blocksize, str quant_type, "
234+
"Tensor? bias=None, Tensor? absmax_8bit=None, Tensor? absmax_code=None, Tensor? absmax_offset=None) -> Tensor",
235+
)
236+
237+
238+
@register_fake("bitsandbytes::gemm_4bit")
239+
def _(
240+
A: torch.Tensor,
241+
B: torch.Tensor,
242+
shapeB: Sequence[int],
243+
absmax: torch.Tensor,
244+
blocksize: int,
245+
quant_type: str,
246+
bias: Optional[torch.Tensor] = None,
247+
absmax_8bit: Optional[torch.Tensor] = None,
248+
absmax_code: Optional[torch.Tensor] = None,
249+
absmax_offset: Optional[torch.Tensor] = None,
250+
) -> torch.Tensor:
251+
torch._check(len(shapeB) == 2, lambda: f"shapeB must be 2D [N, K], got {list(shapeB)}")
252+
torch._check(A.shape[-1] == shapeB[1], lambda: f"A inner dim ({A.shape[-1]}) must match shapeB ({shapeB[1]})")
253+
torch._check(
254+
A.dtype in (torch.float16, torch.bfloat16, torch.float32),
255+
lambda: f"A must be float16, bfloat16, or float32, got {A.dtype}",
256+
)
257+
torch._check(
258+
B.dtype in (torch.uint8, torch.bfloat16, torch.float16, torch.float32),
259+
lambda: f"B must be backed by storage of type uint8, bfloat16, float16, or float32, got {B.dtype}",
260+
)
261+
torch._check(blocksize in [32, 64, 128, 256, 512, 1024, 2048, 4096], lambda: f"invalid blocksize {blocksize}")
262+
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be 'nf4' or 'fp4', got {quant_type!r}")
263+
torch._check(absmax.dtype == torch.float32, lambda: f"absmax must be float32, got {absmax.dtype}")
264+
if absmax_8bit is not None:
265+
torch._check(absmax_8bit.ndim == 1, lambda: f"absmax_8bit must be 1D, got {absmax_8bit.ndim}D")
266+
torch._check(absmax_8bit.dtype == torch.uint8, lambda: f"absmax_8bit must be uint8, got {absmax_8bit.dtype}")
267+
torch._check(absmax_code is not None, lambda: "absmax_code required when absmax_8bit is provided")
268+
torch._check(absmax_code.ndim == 1, lambda: f"absmax_code must be 1D, got {absmax_code.ndim}D")
269+
torch._check(
270+
absmax_code.shape[0] == 256, lambda: f"absmax_code must have 256 entries, got {absmax_code.shape[0]}"
271+
)
272+
torch._check(
273+
absmax_code.dtype == torch.float32, lambda: f"absmax_code must be float32, got {absmax_code.dtype}"
274+
)
275+
torch._check(absmax_offset is not None, lambda: "absmax_offset required when absmax_8bit is provided")
276+
torch._check(
277+
absmax_offset.ndim == 0, lambda: f"absmax_offset must be a scalar (0-dim), got {absmax_offset.ndim}D"
278+
)
279+
torch._check(
280+
absmax_offset.dtype == torch.float32, lambda: f"absmax_offset must be float32, got {absmax_offset.dtype}"
281+
)
282+
if bias is not None:
283+
torch._check(bias.ndim == 1, lambda: f"bias must be 1D, got {bias.ndim}D")
284+
torch._check(bias.shape[0] == shapeB[0], lambda: f"bias length ({bias.shape[0]}) must match N ({shapeB[0]})")
285+
torch._check(bias.dtype == A.dtype, lambda: f"bias dtype ({bias.dtype}) must match A dtype ({A.dtype})")
286+
N = shapeB[0]
287+
return torch.empty((*A.shape[:-1], N), dtype=A.dtype, device=A.device)
288+
289+
231290
torch.library.define(
232291
"bitsandbytes::dequantize_blockwise",
233292
"(Tensor A, Tensor absmax, Tensor code, int blocksize, ScalarType dtype) -> Tensor",

‎bitsandbytes/autograd/_functions.py‎

Lines changed: 108 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,37 @@ def forward(ctx, A, B, out=None, bias=None, quant_state: Optional[F.QuantState]
315315
else:
316316
return torch.empty(A.shape[:-1] + B_shape[:1], dtype=A.dtype, device=A.device)
317317

318-
# 1. Dequantize
319-
# 2. MatmulnN
320-
output = torch.nn.functional.linear(A, F.dequantize_4bit(B, quant_state).to(A.dtype).t(), bias)
318+
# Normalize to canonical [(N*K+1)//2, 1]. Packed weights are always contiguous
319+
# in this orientation (B.t() callers get strides [1,1], still compatible).
320+
# quant_state.shape is the source of truth for N and K.
321+
B = B.view(-1, 1)
322+
323+
if not quant_state.nested:
324+
output = torch.ops.bitsandbytes.gemm_4bit.default(
325+
A,
326+
B,
327+
quant_state.shape,
328+
quant_state.absmax,
329+
quant_state.blocksize,
330+
quant_state.quant_type,
331+
bias=bias,
332+
)
333+
elif quant_state.state2.blocksize == 256:
334+
output = torch.ops.bitsandbytes.gemm_4bit.default(
335+
A,
336+
B,
337+
quant_state.shape,
338+
quant_state.state2.absmax,
339+
quant_state.blocksize,
340+
quant_state.quant_type,
341+
bias=bias,
342+
absmax_8bit=quant_state.absmax,
343+
absmax_code=quant_state.state2.code,
344+
absmax_offset=quant_state.offset,
345+
)
346+
else:
347+
raise NotImplementedError("nested quantization with state2.blocksize != 256 is not supported")
348+
321349
if out is not None:
322350
out.copy_(output)
323351
output = out
@@ -351,7 +379,9 @@ def backward(ctx, grad_output):
351379
# not supported by PyTorch. TODO: create work-around
352380
# if req_gradB: grad_B = torch.matmul(grad_output.t(), A)
353381
if req_gradA:
354-
grad_A = torch.matmul(grad_output, F.dequantize_4bit(B, ctx.state).to(grad_output.dtype).t())
382+
# B in ctx.tensors is already in canonical [(N*K+1)//2, 1] form (normalized in forward).
383+
# dequantize returns [N, K]; matmul(grad_output[M,N], [N,K]) = grad_A[M,K].
384+
grad_A = torch.matmul(grad_output, F.dequantize_4bit(B, ctx.state).to(grad_output.dtype))
355385

356386
return grad_A, grad_B, None, grad_bias, None
357387

@@ -381,26 +411,81 @@ def matmul_4bit(
381411
out: Optional[torch.Tensor] = None,
382412
bias: Optional[torch.Tensor] = None,
383413
):
384-
assert quant_state is not None
385-
if A.device.type == "cpu":
386-
if getattr(quant_state, "packing_format_for_cpu", False):
387-
out = F.gemv_4bit(A, B, out, state=quant_state)
388-
if bias is not None:
389-
out += bias
390-
return out
391-
else:
392-
return MatMul4Bit.apply(A, B, out, bias, quant_state)
393-
394-
if A.numel() == A.shape[-1] and A.requires_grad == False and A.device.type != "hpu":
395-
if A.shape[-1] % quant_state.blocksize != 0:
414+
if quant_state is None:
415+
raise ValueError("quant_state is required")
416+
if len(quant_state.shape) != 2:
417+
raise ValueError("matmul_4bit: quant_state.shape must be 2D [N, K]")
418+
419+
# packing_format_for_cpu uses a different memory layout optimized for AVX512BF16.
420+
# This flag is only set for inference (weight conversion happens at eval time).
421+
# The underlying kernel supports any M via tiled GEMM despite the gemv name.
422+
if A.device.type == "cpu" and getattr(quant_state, "packing_format_for_cpu", False):
423+
result = F.gemv_4bit(A, B, out=out, state=quant_state)
424+
if bias is not None:
425+
result += bias
426+
return result
427+
428+
# Normalize B to canonical [(N*K+1)//2, 1]. Packed weights are always contiguous
429+
# in this orientation (B.t() callers get strides [1,1], still compatible).
430+
# quant_state.shape is the source of truth for N and K.
431+
B = B.view(-1, 1)
432+
433+
K = A.shape[-1]
434+
435+
# Weight is in [K, N] orientation when A's inner dim matches shape[0] not shape[1].
436+
# Square weights (K==N) are ambiguous and treated as [N, K].
437+
if K == quant_state.shape[0] and K != quant_state.shape[1]:
438+
if not _is_compiling():
396439
warn(
397-
f"Some matrices hidden dimension is not a multiple of {quant_state.blocksize} and efficient inference kernels are not supported for these (slow). Matrix input size found: {A.shape}",
440+
f"matmul_4bit: weight was quantized from a [K, N] tensor (quant_state.shape={list(quant_state.shape)}). "
441+
"Re-quantize from the weight in [N, K] (out_features, in_features) orientation. "
442+
"This will be an error in a future version.",
443+
DeprecationWarning,
444+
stacklevel=2,
445+
)
446+
B_dq = F.dequantize_4bit(B, quant_state).to(A.dtype)
447+
result = torch.nn.functional.linear(A, B_dq.t(), bias)
448+
if out is not None:
449+
out.copy_(result)
450+
return out
451+
return result
452+
453+
needs_grad = torch.is_grad_enabled() and (A.requires_grad or (bias is not None and bias.requires_grad))
454+
if not needs_grad:
455+
A_numel = A.numel()
456+
if A_numel == 0:
457+
if out is not None:
458+
return out
459+
return torch.empty((*A.shape[:-1], quant_state.shape[0]), dtype=A.dtype, device=A.device)
460+
461+
if not quant_state.nested:
462+
result = torch.ops.bitsandbytes.gemm_4bit.default(
463+
A,
464+
B,
465+
quant_state.shape,
466+
quant_state.absmax,
467+
quant_state.blocksize,
468+
quant_state.quant_type,
469+
bias=bias,
470+
)
471+
elif quant_state.state2.blocksize == 256:
472+
result = torch.ops.bitsandbytes.gemm_4bit.default(
473+
A,
474+
B,
475+
quant_state.shape,
476+
quant_state.state2.absmax,
477+
quant_state.blocksize,
478+
quant_state.quant_type,
479+
bias=bias,
480+
absmax_8bit=quant_state.absmax,
481+
absmax_code=quant_state.state2.code,
482+
absmax_offset=quant_state.offset,
398483
)
399-
return MatMul4Bit.apply(A, B, out, bias, quant_state)
400484
else:
401-
out = F.gemv_4bit(A, B.t(), out, state=quant_state)
402-
if bias is not None:
403-
out += bias
485+
raise NotImplementedError("nested quantization with state2.blocksize != 256 is not supported")
486+
if out is not None:
487+
out.copy_(result)
404488
return out
405-
else:
406-
return MatMul4Bit.apply(A, B, out, bias, quant_state)
489+
return result
490+
491+
return MatMul4Bit.apply(A, B, out, bias, quant_state)

0 commit comments

Comments
 (0)