You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The AVX-512 build flags are scoped two different ways:
Enablement is target-wide:CMakeLists.txt:375 gates on whether the compiler accepts -mavx512f,
true on any modern GCC regardless of the machine, then applies -mavx512f -mavx512bw -mavx512dq -mavx512vl to the whole bitsandbytes target.
Suppression is per-function: the #pragma GCC target("avx2,fma,no-avx512f") region from Enable CPU Optimizer Support for bitsandbytes #1901
covers the scalar quantize functions that PR added, and nothing else.
GCC can then auto-vectorize any ungated function in those TUs with EVEX instructions. That includes the
paths which exist because the CPU has no AVX-512. dequantizeBlockwise8bitCpu has no AVX-512 branch at
all, and dequantizeBlockwise4bitCpu's scalar fallback is the branch reached when has_avx512f() returns
false. Both get compiled with AVX-512 anyway, so a from-source build on a machine without it dies the
first time one of them runs.
Runtime detection reports correctly here (has_avx512bf16() → 0, backends.cpu.ops._has_avx512 → False). It cannot guard a function that was never an AVX-512 path to begin with.
Repro
Xeon W-1250 (Comet Lake, no AVX-512) / g++ (Debian) 14.2.0 / torch 2.13.0+cpu / Python 3.10.20, built
with RUNNER_OS=Linux RUNNER_ARCH=X64 bash .github/scripts/build-cpu.sh:
compress_statistics=True is crucial; it routes the nested absmax through the 8-bit blockwise
dequant. Same crash from the suite via tests/test_autograd.py::test_matmul_4bit[quant_type=fp4-compress_statistics=T-fp16-has_bias=T-transpose_B=T-req_grad=TFT-dim4=96-dim3=64-dim2=64-cpu].
Under gdb:
Program received signal SIGILL, Illegal instruction.
=> _Z26dequantizeBlockwise8bitCpuIfEvPfPhPKfPT_xx._omp_fn.0+415:
vinsertf32x4 $0x1,%xmm1,%ymm3,%ymm3
#0 dequantizeBlockwise8bitCpu<float>(...) [clone ._omp_fn.0]
#1 GOMP_parallel ()
#2 cdequantize_blockwise_cpu_fp32 () # ctypes, from backends/cpu/ops.py:101
vinsertf32x4 is AVX-512F, but the operands here are ymm/xmm. No zmm, no {%k} mask. So -mprefer-vector-width=256 doesn't prevent it, and a disassembly grep for %zmm/{%k} misses it
outright; that pattern scored the faulting function as clean on my first pass. The reliable check counts
instructions whose encoding starts with the EVEX prefix 0x62: objdump -d libbitsandbytes_cpu.so | grep -c $'\t62 '.
Measured that way, the shipped configuration emits 1264 EVEX instructions inside the intentional gated
kernels (gemv_4bit_inference, tinygemm_kernel_nn) and 130 outside them, spread across dequantizeBlockwise8bitCpu<float,bf16_t>._omp_fn.0, dequantizeBlockwise4bitCpu<float,fp16_t,bf16_t>._omp_fn.0, and the cdequantize_blockwise_cpu_*
wrappers. Forcing HAS_AVX512F_FLAG off takes both counts to zero and the repro passes.
Repairs that don't work
Extending Enable CPU Optimizer Support for bitsandbytes #1901's push_options region upward over the dequantize kernels. No change: still 130, still
SIGILL. These are templates, and their explicit instantiations at cpu_ops.cpp:922–950 sit outside the
region, so codegen happens there under the target-wide options. That probably also limits the existing
Zen3 mitigation wherever a guarded template is instantiated outside its region.
__attribute__((target("avx2,fma,no-avx512f"))) on the definitions. Also no change, byte for byte. cpu_ops.h:324–332 declares both templates without the attribute, and adding it only at the definition
doesn't take.
Neither touches cdequantize_blockwise_cpu_*, which lives in pythonInterface.cpp:766, a second TU the
same flags cover.
Annotation-based scoping would have to be applied across header declarations, definitions and both TUs,
then survive OpenMP outlining on top of that. The ._omp_fn.0 clones hold most of the leaked instructions.
Suggested fix
Compile the AVX-512 kernels in their own translation unit with the -mavx512* flags, via set_source_files_properties(... COMPILE_OPTIONS ...) or a small object library, and leave the rest at -mavx2. That avoids depending on attributes propagating correctly through template instantiation and OMP
outlining. Failing that, a -DBNB_ENABLE_AVX512=OFF opt-out would give affected users a supported
source-build path; I verified that configuration builds clean, emits zero EVEX and passes.
The released wheel is not reproducibly affected. bitsandbytes==0.50.0 passes this test on the same
machine, and I could not fault it across 84 quantize_4bit/dequantize_4bit combinations (bf16/fp32/fp16
× nf4/fp4 × blocksize 64–4096 × plain and nested). Its 8-bit dequant carries no EVEX. It does carry 20
ungated EVEX instructions in dequantizeBlockwise4bitCpu<bf16_t/float>._omp_fn.0 which I could not reach,
so I claim no user impact there, but the shipped artifact's safety currently rests on the release
compiler's vectorization choices rather than on anything structural. This is also one machine; I have no
second non-AVX-512 host to confirm on.
CI being green is consistent with that. #1901 hit this on a non-AVX-512 runner back in March, and the
runners' compiler evidently doesn't vectorize these particular loops to EVEX. The exposure is
compiler-version-dependent rather than closed. A build-time assertion that no EVEX appears outside the
gated kernels would catch the class cheaply.
Found while running the CPU test matrix locally against post-0.50.0 main. Happy to send the PR for
either fix, though the file split touches enough of cpu_ops.cpp that I'd want your preference on layout
first. I have no AVX-512 hardware, so someone would need to check the gated kernels still build and
perform on that side.
The AVX-512 build flags are scoped two different ways:
CMakeLists.txt:375gates on whether the compiler accepts-mavx512f,true on any modern GCC regardless of the machine, then applies
-mavx512f -mavx512bw -mavx512dq -mavx512vlto the wholebitsandbytestarget.#pragma GCC target("avx2,fma,no-avx512f")region from Enable CPU Optimizer Support for bitsandbytes #1901covers the scalar quantize functions that PR added, and nothing else.
GCC can then auto-vectorize any ungated function in those TUs with EVEX instructions. That includes the
paths which exist because the CPU has no AVX-512.
dequantizeBlockwise8bitCpuhas no AVX-512 branch atall, and
dequantizeBlockwise4bitCpu's scalar fallback is the branch reached whenhas_avx512f()returnsfalse. Both get compiled with AVX-512 anyway, so a from-source build on a machine without it dies the
first time one of them runs.
Runtime detection reports correctly here (
has_avx512bf16()→0,backends.cpu.ops._has_avx512→False). It cannot guard a function that was never an AVX-512 path to begin with.Repro
Xeon W-1250 (Comet Lake, no AVX-512) /
g++ (Debian) 14.2.0/ torch 2.13.0+cpu / Python 3.10.20, builtwith
RUNNER_OS=Linux RUNNER_ARCH=X64 bash .github/scripts/build-cpu.sh:compress_statistics=Trueis crucial; it routes the nested absmax through the 8-bit blockwisedequant. Same crash from the suite via
tests/test_autograd.py::test_matmul_4bit[quant_type=fp4-compress_statistics=T-fp16-has_bias=T-transpose_B=T-req_grad=TFT-dim4=96-dim3=64-dim2=64-cpu].Under gdb:
vinsertf32x4is AVX-512F, but the operands here are ymm/xmm. Nozmm, no{%k}mask. So-mprefer-vector-width=256doesn't prevent it, and a disassembly grep for%zmm/{%k}misses itoutright; that pattern scored the faulting function as clean on my first pass. The reliable check counts
instructions whose encoding starts with the EVEX prefix
0x62:objdump -d libbitsandbytes_cpu.so | grep -c $'\t62 '.Measured that way, the shipped configuration emits 1264 EVEX instructions inside the intentional gated
kernels (
gemv_4bit_inference,tinygemm_kernel_nn) and 130 outside them, spread acrossdequantizeBlockwise8bitCpu<float,bf16_t>._omp_fn.0,dequantizeBlockwise4bitCpu<float,fp16_t,bf16_t>._omp_fn.0, and thecdequantize_blockwise_cpu_*wrappers. Forcing
HAS_AVX512F_FLAGoff takes both counts to zero and the repro passes.Repairs that don't work
push_optionsregion upward over the dequantize kernels. No change: still 130, stillSIGILL. These are templates, and their explicit instantiations at
cpu_ops.cpp:922–950sit outside theregion, so codegen happens there under the target-wide options. That probably also limits the existing
Zen3 mitigation wherever a guarded template is instantiated outside its region.
__attribute__((target("avx2,fma,no-avx512f")))on the definitions. Also no change, byte for byte.cpu_ops.h:324–332declares both templates without the attribute, and adding it only at the definitiondoesn't take.
cdequantize_blockwise_cpu_*, which lives inpythonInterface.cpp:766, a second TU thesame flags cover.
Annotation-based scoping would have to be applied across header declarations, definitions and both TUs,
then survive OpenMP outlining on top of that. The
._omp_fn.0clones hold most of the leaked instructions.Suggested fix
Compile the AVX-512 kernels in their own translation unit with the
-mavx512*flags, viaset_source_files_properties(... COMPILE_OPTIONS ...)or a small object library, and leave the rest at-mavx2. That avoids depending on attributes propagating correctly through template instantiation and OMPoutlining. Failing that, a
-DBNB_ENABLE_AVX512=OFFopt-out would give affected users a supportedsource-build path; I verified that configuration builds clean, emits zero EVEX and passes.
The released wheel is not reproducibly affected.
bitsandbytes==0.50.0passes this test on the samemachine, and I could not fault it across 84
quantize_4bit/dequantize_4bitcombinations (bf16/fp32/fp16× nf4/fp4 × blocksize 64–4096 × plain and nested). Its 8-bit dequant carries no EVEX. It does carry 20
ungated EVEX instructions in
dequantizeBlockwise4bitCpu<bf16_t/float>._omp_fn.0which I could not reach,so I claim no user impact there, but the shipped artifact's safety currently rests on the release
compiler's vectorization choices rather than on anything structural. This is also one machine; I have no
second non-AVX-512 host to confirm on.
CI being green is consistent with that. #1901 hit this on a non-AVX-512 runner back in March, and the
runners' compiler evidently doesn't vectorize these particular loops to EVEX. The exposure is
compiler-version-dependent rather than closed. A build-time assertion that no EVEX appears outside the
gated kernels would catch the class cheaply.
Found while running the CPU test matrix locally against post-0.50.0
main. Happy to send the PR foreither fix, though the file split touches enough of
cpu_ops.cppthat I'd want your preference on layoutfirst. I have no AVX-512 hardware, so someone would need to check the gated kernels still build and
perform on that side.