Skip to content

Add BF16 support for int8_vectorwise_quant / LLM.int8 activation quant#1985

Open
kru2710shna wants to merge 4 commits into
bitsandbytes-foundation:mainfrom
kru2710shna:feature/int8-bf16-vectorwise-quant
Open

Add BF16 support for int8_vectorwise_quant / LLM.int8 activation quant#1985
kru2710shna wants to merge 4 commits into
bitsandbytes-foundation:mainfrom
kru2710shna:feature/int8-bf16-vectorwise-quant

Conversation

@kru2710shna

Copy link
Copy Markdown

Templates int8VectorQuant on T and adds bf16 kernel instantiations plus a cint8_vector_quant_bf16 C ABI entry point, mirroring the existing gemm_4bit_inference_naive fp16/bf16/fp32 pattern. The blockwise absmax reduction now accumulates in float rather than T: required for bf16 to compile cleanly and slightly improves fp16 accuracy (rowStats was already float, so downstream is unaffected). Removes the forced A.to(torch.float16) casts in MatMul8bitLt so bf16 activations quantize natively.

Closes #1868.

Comment thread csrc/kernels.cu
Comment thread csrc/kernels.cu
Comment thread bitsandbytes/autograd/_functions.py Outdated
@matthewdouglas

Copy link
Copy Markdown
Member

I think it would be worthwhile to update some of the other relevant tests to exercise the new bf16 path. E.g. in test_functional.py, test_linear8bit.py, test_modules.py, test_autograd.py where appropriate.

@kru2710shna

Copy link
Copy Markdown
Author

Will do
I'll add bf16 coverage to the higher-level tests (test_functional.py, test_linear8bit.py, test_modules.py, test_autograd.py) in a follow-up commit on this branch.

Templates int8VectorQuant on T and adds bf16 kernel instantiations plus a
cint8_vector_quant_bf16 C ABI entry point, mirroring the existing
gemm_4bit_inference_naive fp16/bf16/fp32 pattern. The blockwise absmax
reduction now accumulates in float rather than T: required for bf16 to
compile cleanly and slightly improves fp16 accuracy (rowStats was already
float, so downstream is unaffected). Removes the forced A.to(torch.float16)
casts in MatMul8bitLt so bf16 activations quantize natively.

Closes bitsandbytes-foundation#1868.
…rning

- Restore explanatory comments in kInt8VectorQuant that were dropped during
  the float-accumulation rewrite (striped-load pattern, sparse-decomp absmax,
  outlier zeroing).
- Remove the 'inputs will be cast to float16' warning in MatMul8bitLt, which
  no longer applies now that the forced fp16 cast is gone.
@kru2710shna kru2710shna force-pushed the feature/int8-bf16-vectorwise-quant branch from e30d08c to cdd9e23 Compare July 7, 2026 09:21
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

Extends existing tests to exercise the new bf16 activation-quant path:
- test_matmullt: drop the forced fp16 cast so the existing bf16 param
  actually exercises the bf16 kernel end-to-end.
- test_int8_linear_matmul_half: parametrize over fp16/bf16.
- test_linear8bitlt_inference and new test_linear8bitlt_forward_dtypes:
  exercise the Linear8bitLt module forward in both dtypes.

Addresses review feedback on bitsandbytes-foundation#1985.
@kru2710shna

Copy link
Copy Markdown
Author

Done in #1985 - e888a86. Added bf16 coverage across the relevant tests:

test_matmullt: dropped the forced fp16 cast so the existing bf16 param now exercises the real path end-to-end.
test_int8_linear_matmul_half and test_linear8bitlt_inference: parametrized over fp16/bf16.
New test_linear8bitlt_forward_dtypes for the module forward in both dtypes.

Note: test_linear8bit.py didn't exist, so I covered test_linear8bitlt.py instead. Also rebased onto latest main.

Happy to adjust anything.

@matthewdouglas

Copy link
Copy Markdown
Member

There's still a test failure and a couple build issues to resolve.

bitsandbytes.functional.int8_mm_dequant right now hard-codes the output dtype to be torch.float16, so I think at the very least in this test we'd consider casting this. A more thorough support of bfloat16 throughout would involve implementing bfloat16 output and bias addition directly in the int8_mm_dequant kernel and adding support for the dtype kwarg to bitsandbytes.functional.int8_mm_dequant as well.

Templates kdequant_mm_int32_fp16 and its launcher on output dtype, adds a
cdequant_mm_int32_bf16 C ABI entry point, and dispatches on dtype in the
Python backend. Fused bias now works for both fp16 and bf16 when the bias
dtype matches the output dtype.

Exposes a dtype kwarg on bitsandbytes.functional.int8_mm_dequant so callers
can request bf16 output directly instead of relying on a cast afterward.

Reverts the earlier test workaround now that int8_mm_dequant genuinely
returns bf16 when requested.

Addresses review feedback on bitsandbytes-foundation#1985 (previously deferred as a follow-up).
@kru2710shna

Copy link
Copy Markdown
Author

Went ahead and implemented this properly rather than just patching the test. int8_mm_dequant now natively supports bfloat16 output end-to-end:

Templated kdequant_mm_int32_fp16 and its launcher on output dtype, added a bnb_bfloat16 instantiation and a cdequant_mm_int32_bf16 C ABI entry.
The Python backend dispatches on the requested dtype and allocates out directly in that dtype.
Fused bias now works for bf16 too (previously fp16-only), as it fuses in-kernel whenever bias.dtype matches the output dtype, otherwise added afterward as before.
bitsandbytes.functional.int8_mm_dequant now takes a dtype kwarg instead of hardcoding fp16.
Reverted the test workaround, this test_int8_linear_matmul_half now asserts a real bf16-to-bf16 comparison.

This should close the last known gap for bf16 support in this PR.

Comment thread csrc/ops.cu
Comment on lines +493 to +499
template void dequant_mm_int32_fp16<half>(
int* A, float* rowStats, float* colStats, half* out, half* bias, int numRows, int numCols, bnb_stream_t stream
);
template void dequant_mm_int32_fp16<bnb_bfloat16>(
int* A, float* rowStats, float* colStats, bnb_bfloat16* out, bnb_bfloat16* bias, int numRows, int numCols,
bnb_stream_t stream
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that it supports bf16 and fp16, the naming on this with the _fp16 suffix is a little confusing. It may make more sense to just rename it to dequant_mm_int32<T>.

torch.testing.assert_close(out_accelerator_2, out_accelerator, rtol=1e-8, atol=1e-8)


@pytest.mark.parametrize("device", get_available_devices(no_cpu=True))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why skip on CPU?

@matthewdouglas

Copy link
Copy Markdown
Member

Hi @kru2710shna,

There still remains build failures on CUDA < 12.4 and on ROCm. Additionally there are test failures where the build succeeded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Question: intentional FP16-only path for int8_vectorwise_quant / LLM.int8 activation quant? (BF16 support + removing casts)

2 participants