Regression tests for #1342 and #1690: quantization must handle non-contiguous tensors correctly.
| 376 | |
| 377 | |
| 378 | class TestNonContiguousInputs: |
| 379 | """Regression tests for #1342 and #1690: quantization must handle non-contiguous tensors correctly.""" |
| 380 | |
| 381 | @pytest.mark.parametrize("device", get_available_devices()) |
| 382 | @pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32], ids=id_formatter("dtype")) |
| 383 | @pytest.mark.parametrize("blocksize", [64, 128, 256]) |
| 384 | def test_quantize_blockwise_non_contiguous(self, device, dtype, blocksize): |
| 385 | code = bitsandbytes.functional.create_dynamic_map().to(device) |
| 386 | |
| 387 | # Create non-contiguous tensor via slicing |
| 388 | A_full = torch.randn(3, 4, 6, 256, dtype=dtype, device=device) |
| 389 | A_noncontig = A_full[:, ::2, :, :] |
| 390 | assert not A_noncontig.is_contiguous() |
| 391 | |
| 392 | A_contig = A_noncontig.contiguous() |
| 393 | |
| 394 | out_nc, absmax_nc = torch.ops.bitsandbytes.quantize_blockwise(A_noncontig, code, blocksize) |
| 395 | out_c, absmax_c = torch.ops.bitsandbytes.quantize_blockwise(A_contig, code, blocksize) |
| 396 | |
| 397 | torch.testing.assert_close(absmax_nc, absmax_c) |
| 398 | torch.testing.assert_close(out_nc, out_c) |
| 399 | |
| 400 | @pytest.mark.parametrize("device", get_available_devices()) |
| 401 | @pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32], ids=id_formatter("dtype")) |
| 402 | @pytest.mark.parametrize("blocksize", [64, 128, 256]) |
| 403 | def test_dequantize_blockwise_non_contiguous(self, device, dtype, blocksize): |
| 404 | code = bitsandbytes.functional.create_dynamic_map().to(device, dtype=torch.float32) |
| 405 | |
| 406 | # Quantize a contiguous tensor, then create non-contiguous uint8 via transpose |
| 407 | A = torch.randn(1024, 1024, dtype=dtype, device=device) |
| 408 | quantized, absmax = torch.ops.bitsandbytes.quantize_blockwise(A, code, blocksize) |
| 409 | |
| 410 | # Create non-contiguous uint8 tensor by transposing and transposing back |
| 411 | q_noncontig = quantized.t().t() |
| 412 | # If that's still contiguous, use a different approach |
| 413 | if q_noncontig.is_contiguous(): |
| 414 | # Pad and slice to force non-contiguity |
| 415 | q_padded = torch.zeros(1024, 1025, dtype=torch.uint8, device=device) |
| 416 | q_padded[:, :1024] = quantized |
| 417 | q_noncontig = q_padded[:, :1024] |
| 418 | |
| 419 | assert not q_noncontig.is_contiguous() |
| 420 | q_contig = q_noncontig.contiguous() |
| 421 | |
| 422 | out_nc = torch.ops.bitsandbytes.dequantize_blockwise(q_noncontig, absmax, code, blocksize, dtype) |
| 423 | out_c = torch.ops.bitsandbytes.dequantize_blockwise(q_contig, absmax, code, blocksize, dtype) |
| 424 | |
| 425 | torch.testing.assert_close(out_nc, out_c) |
| 426 | |
| 427 | @pytest.mark.parametrize("device", get_available_devices()) |
| 428 | @pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32], ids=id_formatter("dtype")) |
| 429 | @pytest.mark.parametrize("quant_type", ["fp4", "nf4"]) |
| 430 | @pytest.mark.parametrize("blocksize", [64, 128, 256]) |
| 431 | def test_quantize_4bit_non_contiguous(self, device, dtype, quant_type, blocksize): |
| 432 | if device not in ("cuda", "mps"): |
| 433 | pytest.skip("Non-contiguous input handling not implemented for this backend") |
| 434 | |
| 435 | # Reproduce issue #1342: non-contiguous tensor from slicing |
nothing calls this directly
no outgoing calls
no test coverage detected