Repository navigation
Fix XPU quantize_4bit returning padded tensors that violate the op contract - #2099
Open
jiqing-feng wants to merge 1 commit into
Open
jiqing-feng wants to merge 1 commit into
jiqing-feng wants to merge 1 commit into
Conversation
…ntract The Triton quantize_4bit path pads the input up to a multiple of blocksize and then sizes its outputs from the padded element count. The padded sizes leak into the returned tensors, so on XPU both `packed` and `absmax` come back larger than the shapes declared by register_fake in bitsandbytes/_ops.py whenever numel() is not a multiple of blocksize. For a 20x20 fp16 tensor with blocksize=64 the fake kernel, the CPU backend and the CUDA backend all return packed (200, 1) / absmax (7,), but XPU returns packed (224, 1) / absmax (8,). This breaks meta tracing and torch.compile, makes 4-bit checkpoints non-portable between XPU and CPU/CUDA, and causes downstream shape validation (for example in diffusers) to reject XPU-quantized weights. The padding is unnecessary: the kernels in backends/triton/kernels_4bit.py already mask both their loads and their stores. Drop it, keep absmax over-allocated to an even block count since the kernel processes blocks in pairs, and trim it before returning. Size the packed byte buffer from the real element count, rounding up only as far as needed to keep the quant_storage view legal, then slice to the declared length - this also fixes odd element counts combined with a non-uint8 quant_storage, which the padding used to mask. Quantized bytes are bit-identical to before for inputs divisible by blocksize, and identical on the valid prefix otherwise.
Contributor
Author
|
Hi @matthewdouglas . Would you please review this PR? Thanks! |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On XPU,
torch.ops.bitsandbytes.quantize_4bitreturns tensors whose shapes do not match the ones declared by itsregister_fakeimplementation (and produced by the CPU/CUDA backends) whenever the input element count is not a multiple ofblocksize.Before:
After:
Root cause
bitsandbytes/backends/triton/ops.py::quantize_4bitpads the input up to the next multiple ofblocksizeand then derives the output sizes from the padded element count, so the padding leaks into the returned shapes:The padding is unnecessary: the Triton kernel already receives
num_elements=nand masks out-of-range lanes itself.Fix
Drop the padding and size the outputs from the real element count.
absmaxis still allocated to an even block count because the kernel walks blocks in pairs, and is trimmed to the real block count before returning. The packed buffer is rounded up to a whole number ofquant_storageelements so the finalview(quant_storage)stays valid, then sliced to the size the fake kernel promises.This also fixes a latent crash the padding used to hide: with an odd element count and a non-
uint8quant_storage, the reinterpreting view raisedRuntimeError: self.size(-1) must be divisible by 2 to view Byte as Half.triton_ops.quantize_4bitis only registered for"xpu"; CPU, CUDA, MPS and HPU have their own implementations and are unaffected.Verification
Hardware: Intel Arc Pro B60,
torch 2.14.0+xpu.Shape contract against
register_fake, across shapes, blocksizes, quant types, dtypes andquant_storagevariants: 47 failures before, all passing after.No numerical change. Quantized bytes were compared bit-for-bit between stock and patched builds over 144 combinations (8 shapes x 3 blocksizes x 2 quant types x 3 dtypes) with a fixed seed: the 54 cases divisible by
blocksizeare bit-identical, and the 90 non-divisible cases are bit-identical over the non-padding prefix.Performance is unchanged for divisible shapes and improves for non-divisible ones, since the pad allocation and copy are gone (200 iterations x 5 repeats, fp4):
Test suite: