Fix bitsandbytes zombie module breaking test collection on CPU runners (#7580)

* Fix bitsandbytes zombie module breaking test collection

A partially failed `import bitsandbytes` leaves the package half-imported:
CPython evicts only the parent from sys.modules and keeps every submodule it
had already loaded. The next import re-executes __init__ but every
`from .x import y` is served from cache, so the submodule attributes are never
rebound. The package imports "successfully" while `bnb.functional` is gone.

Bind the submodule via `import bitsandbytes.functional as bnb_functional`,
which reads sys.modules directly and survives that state, and import
bitsandbytes in tests/conftest.py on the real CPU path before
torch.cuda.is_available() is mocked, so the half-imported state is never
created in the first place.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Daniel Han 2026-07-28 21:18:05 -07:00 committed by GitHub
commit 7b068090b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 23 additions and 10 deletions

View file

@ -303,13 +303,19 @@ if DEVICE_TYPE == "cuda":
# Try loading bitsandbytes and triton # Try loading bitsandbytes and triton
try: try:
import bitsandbytes as bnb import bitsandbytes as bnb
# Bind the submodule by name: a half-imported bitsandbytes leaves the parent
# without a `functional` attribute, which would otherwise be misreported below
# as a CUDA linking failure. See unsloth/kernels/utils.py.
import bitsandbytes.functional as bnb_functional
except: except:
print( print(
"Unsloth: `bitsandbytes` is not installed - 4bit QLoRA unallowed, but 16bit and full finetuning works!" "Unsloth: `bitsandbytes` is not installed - 4bit QLoRA unallowed, but 16bit and full finetuning works!"
) )
bnb = None bnb = None
bnb_functional = None
try: try:
cdequantize_blockwise_fp32 = bnb.functional.lib.cdequantize_blockwise_fp32 cdequantize_blockwise_fp32 = bnb_functional.lib.cdequantize_blockwise_fp32
libcuda_dirs() libcuda_dirs()
except: except:
if hasattr(os, "geteuid") and os.geteuid() == 0: if hasattr(os, "geteuid") and os.geteuid() == 0:
@ -351,7 +357,7 @@ if DEVICE_TYPE == "cuda":
pass pass
else: else:
from triton.common.build import libcuda_dirs from triton.common.build import libcuda_dirs
cdequantize_blockwise_fp32 = bnb.functional.lib.cdequantize_blockwise_fp32 cdequantize_blockwise_fp32 = bnb_functional.lib.cdequantize_blockwise_fp32
libcuda_dirs() libcuda_dirs()
except: except:
warnings.warn( warnings.warn(

View file

@ -136,11 +136,18 @@ def calculate_settings(
HAS_CUDA_STREAM = False HAS_CUDA_STREAM = False
try: try:
import bitsandbytes as bnb import bitsandbytes as bnb
# If an earlier `import bitsandbytes` died inside __init__, CPython evicts only
# the parent from sys.modules and keeps its submodules, so this retry re-executes
# __init__ without rebinding `bnb.functional`. `import x.y as z` reads sys.modules
# directly and survives that, plain attribute access does not.
import bitsandbytes.functional as bnb_functional
except Exception: except Exception:
# device_type.py already degrades to 16bit/full finetuning when bnb is missing # device_type.py already degrades to 16bit/full finetuning when bnb is missing
# (e.g. gfx906, whose generic wheel has no kernels). Keep the import working and # (e.g. gfx906, whose generic wheel has no kernels). Keep the import working and
# fail only if a 4bit path is actually entered. # fail only if a 4bit path is actually entered.
bnb = None bnb = None
bnb_functional = None
def _bnb_required(*args, **kwargs): def _bnb_required(*args, **kwargs):
@ -153,7 +160,7 @@ def _bnb_required(*args, **kwargs):
if bnb is not None: if bnb is not None:
# https://github.com/bitsandbytes-foundation/bitsandbytes/pull/1330/files # https://github.com/bitsandbytes-foundation/bitsandbytes/pull/1330/files
HAS_CUDA_STREAM = Version(bnb.__version__) > Version("0.43.3") HAS_CUDA_STREAM = Version(bnb.__version__) > Version("0.43.3")
get_ptr = bnb.functional.get_ptr get_ptr = bnb_functional.get_ptr
else: else:
get_ptr = _bnb_required get_ptr = _bnb_required
@ -263,18 +270,18 @@ if bnb is None or not native_kernels_ready(bnb, DEVICE_TYPE):
cgemm_4bit_inference_naive_fp16 = _bnb_required cgemm_4bit_inference_naive_fp16 = _bnb_required
cgemm_4bit_inference_naive_bf16 = _bnb_required cgemm_4bit_inference_naive_bf16 = _bnb_required
else: else:
cdequantize_blockwise_fp32 = bnb.functional.lib.cdequantize_blockwise_fp32 cdequantize_blockwise_fp32 = bnb_functional.lib.cdequantize_blockwise_fp32
cdequantize_blockwise_fp16_nf4 = bnb.functional.lib.cdequantize_blockwise_fp16_nf4 cdequantize_blockwise_fp16_nf4 = bnb_functional.lib.cdequantize_blockwise_fp16_nf4
cdequantize_blockwise_bf16_nf4 = bnb.functional.lib.cdequantize_blockwise_bf16_nf4 cdequantize_blockwise_bf16_nf4 = bnb_functional.lib.cdequantize_blockwise_bf16_nf4
if DEVICE_TYPE == "xpu": if DEVICE_TYPE == "xpu":
# https://github.com/bitsandbytes-foundation/bitsandbytes/blob/c3b8de268fdb55a88f92feada23fc811a1e6877a/bitsandbytes/backends/xpu/ops.py#L115 # https://github.com/bitsandbytes-foundation/bitsandbytes/blob/c3b8de268fdb55a88f92feada23fc811a1e6877a/bitsandbytes/backends/xpu/ops.py#L115
# for xpu, inference gemv using above link # for xpu, inference gemv using above link
cgemm_4bit_inference_naive_fp16 = bnb.functional.lib.cgemv_4bit_inference_fp16 cgemm_4bit_inference_naive_fp16 = bnb_functional.lib.cgemv_4bit_inference_fp16
cgemm_4bit_inference_naive_bf16 = bnb.functional.lib.cgemv_4bit_inference_bf16 cgemm_4bit_inference_naive_bf16 = bnb_functional.lib.cgemv_4bit_inference_bf16
else: else:
cgemm_4bit_inference_naive_fp16 = bnb.functional.lib.cgemm_4bit_inference_naive_fp16 cgemm_4bit_inference_naive_fp16 = bnb_functional.lib.cgemm_4bit_inference_naive_fp16
cgemm_4bit_inference_naive_bf16 = bnb.functional.lib.cgemm_4bit_inference_naive_bf16 cgemm_4bit_inference_naive_bf16 = bnb_functional.lib.cgemm_4bit_inference_naive_bf16
torch_device_stream = ( torch_device_stream = (