Skip to content

Commit 0a44931

Browse files
committed
Fix: ensure absmax_offset is of type float32 before passing to gemm_4bit kernel
1 parent 9dad665 commit 0a44931

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

bitsandbytes/backends/cuda/ops.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,14 +867,17 @@ def _(
867867
else:
868868
raise RuntimeError(f"unsupported dtype {A.dtype}")
869869

870+
# Offset is expected to be a float32 tensor.
871+
absmax_offset_f32 = absmax_offset.to(dtype=torch.float32) if absmax_offset is not None else None
872+
870873
with _cuda_device_of(A):
871874
fn(
872875
A.data_ptr(),
873876
B.data_ptr(),
874877
absmax.data_ptr(),
875878
absmax_8bit.data_ptr() if absmax_8bit is not None else None,
876879
absmax_code.data_ptr() if absmax_code is not None else None,
877-
absmax_offset.data_ptr() if absmax_offset is not None else None,
880+
absmax_offset_f32.data_ptr() if absmax_offset_f32 is not None else None,
878881
out.data_ptr(),
879882
bias.data_ptr() if bias is not None else None,
880883
M,

tests/test_ops.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,50 @@ def test_gemm_4bit(self, device, dtype, quant_type, compress_statistics, has_bia
333333
kwargs={"bias": bias},
334334
)
335335

336+
@pytest.mark.parametrize("device", get_available_devices())
337+
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16], ids=describe_dtype)
338+
@pytest.mark.parametrize("offset_dtype", [torch.float16, torch.bfloat16], ids=describe_dtype)
339+
def test_gemm_4bit_non_float32_offset(self, device, dtype, offset_dtype):
340+
"""Regression test: offset tensors not in float32 must still produce correct results.
341+
342+
Pre-quantized models (e.g. Unsloth bnb-4bit) may store qs.offset in non-float32 dtype.
343+
"""
344+
N, K, blocksize = 64, 64, 64
345+
A = torch.randn(4, K, dtype=dtype, device=device)
346+
B = torch.randn(N, K, dtype=dtype, device=device)
347+
B_q, qs = bitsandbytes.functional.quantize_4bit(
348+
B, blocksize=blocksize, quant_type="nf4", compress_statistics=True
349+
)
350+
351+
# Simulate a pre-quantized model where offset may not be float32.
352+
offset_non_f32 = qs.offset.to(dtype=offset_dtype)
353+
354+
# Reference: explicitly use the rounded float32 value.
355+
offset_as_f32 = offset_non_f32.to(dtype=torch.float32)
356+
ref = torch.ops.bitsandbytes.gemm_4bit.default(
357+
A,
358+
B_q,
359+
list(B.shape),
360+
qs.state2.absmax,
361+
blocksize,
362+
"nf4",
363+
absmax_8bit=qs.absmax,
364+
absmax_code=qs.state2.code,
365+
absmax_offset=offset_as_f32,
366+
)
367+
out = torch.ops.bitsandbytes.gemm_4bit.default(
368+
A,
369+
B_q,
370+
list(B.shape),
371+
qs.state2.absmax,
372+
blocksize,
373+
"nf4",
374+
absmax_8bit=qs.absmax,
375+
absmax_code=qs.state2.code,
376+
absmax_offset=offset_non_f32,
377+
)
378+
torch.testing.assert_close(out, ref)
379+
336380

337381
class TestNonContiguousInputs:
338382
"""Regression tests for #1342 and #1690: quantization must handle non-contiguous tensors correctly."""

0 commit comments

Comments
 (0)