| import ctypes as ct |
| import functools |
| import logging |
| import os |
| from pathlib import Path |
| import re |
| from typing import Optional |
|
|
| import torch |
|
|
| from bitsandbytes.consts import DYNAMIC_LIBRARY_SUFFIX, PACKAGE_DIR |
| from bitsandbytes.cuda_specs import ( |
| CUDASpecs, |
| get_cuda_specs, |
| get_cuda_version_tuple, |
| get_rocm_gpu_arch, |
| get_rocm_warpsize, |
| ) |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def get_cuda_bnb_library_path(cuda_specs: CUDASpecs) -> Path: |
| """ |
| Get the disk path to the CUDA BNB native library specified by the |
| given CUDA specs, taking into account the `BNB_CUDA_VERSION` override environment variable. |
| |
| The library is not guaranteed to exist at the returned path. |
| """ |
|
|
| prefix = "rocm" if torch.version.hip else "cuda" |
| library_name = f"libbitsandbytes_{prefix}{cuda_specs.cuda_version_string}{DYNAMIC_LIBRARY_SUFFIX}" |
|
|
| override_value = os.environ.get("BNB_CUDA_VERSION") |
| if override_value: |
| library_name = re.sub(r"cuda\d+", f"cuda{override_value}", library_name, count=1) |
| if torch.version.hip: |
| raise RuntimeError( |
| f"BNB_CUDA_VERSION={override_value} detected for ROCm!! \n" |
| f"Clear the variable and retry: export BNB_CUDA_VERSION=\n" |
| ) |
| logger.warning( |
| f"WARNING: BNB_CUDA_VERSION={override_value} environment variable detected; loading {library_name}.\n" |
| "This can be used to load a bitsandbytes version built with a CUDA version that is different from the PyTorch CUDA version.\n" |
| "If this was unintended set the BNB_CUDA_VERSION variable to an empty string: export BNB_CUDA_VERSION=\n" |
| ) |
|
|
| return PACKAGE_DIR / library_name |
|
|
|
|
| class BNBNativeLibrary: |
| _lib: ct.CDLL |
| compiled_with_cuda = False |
|
|
| def __init__(self, lib: ct.CDLL): |
| self._lib = lib |
|
|
| @functools.cache |
| def __getattr__(self, name): |
| fn = getattr(self._lib, name, None) |
|
|
| if fn is not None: |
| return fn |
|
|
| def throw_on_call(*args, **kwargs): |
| raise RuntimeError( |
| f"Method '{name}' not available in CPU-only version of bitsandbytes.\n" |
| "Reinstall with GPU support or use CUDA-enabled hardware." |
| ) |
|
|
| return throw_on_call |
|
|
| def __getitem__(self, item): |
| return self.__getattr__(item) |
|
|
|
|
| class CudaBNBNativeLibrary(BNBNativeLibrary): |
| compiled_with_cuda = True |
|
|
| def __init__(self, lib: ct.CDLL): |
| super().__init__(lib) |
| lib.get_context.restype = ct.c_void_p |
| lib.get_cusparse.restype = ct.c_void_p |
| lib.cget_managed_ptr.restype = ct.c_void_p |
|
|
|
|
| def get_available_cuda_binary_versions() -> list[str]: |
| """Get formatted CUDA versions from existing library files using cuda_specs logic""" |
| lib_pattern = f"libbitsandbytes_{BNB_BACKEND.lower()}*{DYNAMIC_LIBRARY_SUFFIX}" |
| versions = [] |
| for lib in Path(__file__).parent.glob(lib_pattern): |
| pattern = rf"{BNB_BACKEND.lower()}(\d+)" |
| match = re.search(pattern, lib.name) |
| if match: |
| ver_code = int(match.group(1)) |
| major = ver_code // 10 |
| minor = ver_code % 10 |
| versions.append(f"{major}.{minor}") |
| return sorted(versions) |
|
|
|
|
| def parse_cuda_version(version_str: str) -> str: |
| """Convert raw version string (e.g. '118' from env var) to formatted version (e.g. '11.8')""" |
| if version_str.isdigit(): |
| return f"{version_str[:-1]}.{version_str[-1]}" |
| return version_str |
|
|
|
|
| class ErrorHandlerMockBNBNativeLibrary(BNBNativeLibrary): |
| """ |
| Mock library handler that defers errors until native methods are called. |
| |
| This class serves as a fallback when the native bitsandbytes library fails to load. |
| It captures the original error and generates detailed troubleshooting guidance. |
| |
| Key behaviors: |
| - Allows attribute access and method assignment without immediate errors |
| - Throws a RuntimeError with diagnostic information only when a native method is called, as otherwise it would error out on import, breaking backward compatibility |
| - Handles both missing CUDA dependencies and version mismatch scenarios |
| |
| Error scenarios covered: |
| 1. Missing shared library dependencies (e.g., libcudart.so not in LD_LIBRARY_PATH or through PyTorch CUDA installation) |
| 2. CUDA version mismatch between PyTorch and available pre-compiled binaries |
| 3. Completely missing pre-compiled binaries when CUDA is detected |
| 4. Custom BNB_CUDA_VERSION override but mismatch |
| 5. CPU-only installation attempts when GPU functionality is requested |
| |
| """ |
|
|
| def __init__(self, error_msg: str): |
| self.error_msg = error_msg |
| self.user_cuda_version = get_cuda_version_tuple() |
| self.available_versions = get_available_cuda_binary_versions() |
| self.override_value = os.environ.get("BNB_CUDA_VERSION") |
| self.requested_version = ( |
| parse_cuda_version(self.override_value) |
| if self.override_value |
| else f"{self.user_cuda_version[0]}.{self.user_cuda_version[1]}" |
| if self.user_cuda_version |
| else "unknown" |
| ) |
|
|
| |
| if "cannot open shared object file" in error_msg: |
| self.formatted_error = self._format_dependency_error() |
| else: |
| self.formatted_error = self._format_lib_error_message( |
| available_versions=self.available_versions, |
| user_cuda_version=f"{self.user_cuda_version[0]}.{self.user_cuda_version[1]}" |
| if self.user_cuda_version |
| else "unknown", |
| original_error=f"Original error: {self.error_msg}\n" if self.error_msg else "", |
| requested_version=self.requested_version, |
| ) |
|
|
| def _format_lib_error_message( |
| self, |
| available_versions: list[str], |
| user_cuda_version: str, |
| original_error: str = "", |
| requested_version: Optional[str] = None, |
| ) -> str: |
| """Format detailed error message for library loading failures""" |
| analysis = "" |
| no_cpu_lib_found = "libbitsandbytes_cpu.so: cannot open" in original_error |
| no_cuda_lib_found = f"{BNB_BACKEND} binary not found" in original_error |
|
|
| if no_cpu_lib_found: |
| analysis = "\n🚨 Failed to load CPU-only bitsandbytes library 🚨\n\n" |
|
|
| elif no_cuda_lib_found: |
| version_list_str = "\n - " + "\n - ".join(available_versions) if available_versions else "NONE" |
| analysis = ( |
| ( |
| f"\n🚨 {BNB_BACKEND} VERSION MISMATCH 🚨\n" |
| f"Requested {BNB_BACKEND} version: {requested_version}\n" |
| f"Detected PyTorch {BNB_BACKEND} version: {user_cuda_version}\n" |
| f"Available pre-compiled versions: {version_list_str}\n\n" |
| "This means:\n" |
| "The version you're trying to use is NOT distributed with this package\n\n" |
| ) |
| if available_versions |
| else "\n🚨 Forgot to compile the bitsandbytes library? 🚨\n" |
| "1. You're not using the package but checked-out the source code\n" |
| "2. You MUST compile from source\n\n" |
| ) |
|
|
| base_msg = "Attempted to use bitsandbytes native library functionality but it's not available.\n\n" |
|
|
| troubleshooting = ( |
| ( |
| f"This typically happens when:\n" |
| f"1. bitsandbytes doesn't ship with a pre-compiled binary for your {BNB_BACKEND} version\n" |
| f"2. The library wasn't compiled properly during installation from source\n\n" |
| ) |
| if no_cuda_lib_found |
| else f"This typically happens when you checked the code out from source and your torch installation doesn't detect {BNB_BACKEND} on your machine.\n\n" |
| ) |
|
|
| note = ( |
| ( |
| f"To make bitsandbytes work, the compiled library version MUST exactly match the linked {BNB_BACKEND} version.\n" |
| f"If your {BNB_BACKEND} version doesn't have a pre-compiled binary, you MUST compile from source.\n\n" |
| ) |
| if no_cuda_lib_found |
| else "" |
| ) |
|
|
| compile_instructions = ( |
| ("COMPILE FROM SOURCE for CPU-only:\n `cmake -DCOMPUTE_BACKEND=cpu -S . && make`\n\n") |
| if not no_cuda_lib_found |
| else ( |
| "You have two options:\n" |
| "1. COMPILE FROM SOURCE (required if no binary exists):\n" |
| " https://huggingface.co/docs/bitsandbytes/main/en/installation#cuda-compile\n" |
| "2. Use BNB_CUDA_VERSION to specify a DIFFERENT CUDA version from the detected one, which is installed on your machine and matching an available pre-compiled version listed above\n\n" |
| ) |
| if not HIP_ENVIRONMENT |
| else ( |
| "You can COMPILE FROM SOURCE as mentioned here:\n" |
| " https://huggingface.co/docs/bitsandbytes/main/en/installation?backend=AMD+ROCm#amd-gpu\n" |
| ) |
| ) |
|
|
| diagnostics = ( |
| f"🔍 Run this command for detailed diagnostics:\n" |
| f"python -m bitsandbytes\n\n" |
| f"If you've tried everything and still have issues:\n" |
| f"1. Include ALL version info (operating system, bitsandbytes, pytorch, {BNB_BACKEND.lower()}, python)\n" |
| f"2. Describe what you've tried in detail\n" |
| f"3. Open an issue with this information:\n" |
| f" https://github.com/bitsandbytes-foundation/bitsandbytes/issues\n\n" |
| ) |
|
|
| return f"{analysis}{base_msg}{troubleshooting}{note}{compile_instructions}{original_error}\n{diagnostics}" |
|
|
| def _format_dependency_error(self) -> str: |
| """Format error message for missing shared libraries""" |
| |
| error_parts = self.error_msg.split(":") |
| missing_lib = error_parts[0].strip() if len(error_parts) > 0 else "unknown library" |
| cuda_major_version = ( |
| self.requested_version.split(".")[0] if "." in self.requested_version else self.requested_version |
| ) |
|
|
| return ( |
| f"\n🚨 {BNB_BACKEND} SETUP ERROR: Missing dependency: {missing_lib} 🚨\n\n" |
| f"{BNB_BACKEND} {cuda_major_version}.x runtime libraries were not found in the LD_LIBRARY_PATH.\n\n" |
| f"To fix this, make sure that:\n" |
| f"1. You have installed {BNB_BACKEND} {cuda_major_version}.x toolkit on your system\n" |
| f"2. The {BNB_BACKEND} runtime libraries are in your LD_LIBRARY_PATH\n\n" |
| f"You can add them with (and persist the change by adding the line to your .bashrc):\n" |
| f" export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/{BNB_BACKEND.lower()}-{cuda_major_version}.x/\ |
| {'lib64' if not HIP_ENVIRONMENT else 'lib'}\n\n" |
| f"Original error: {self.error_msg}\n\n" |
| f"🔍 Run this command for detailed diagnostics:\n" |
| f"python -m bitsandbytes\n\n" |
| f"If you've tried everything and still have issues:\n" |
| f"1. Include ALL version info (operating system, bitsandbytes, pytorch, {BNB_BACKEND.lower()}, python)\n" |
| f"2. Describe what you've tried in detail\n" |
| f"3. Open an issue with this information:\n" |
| f" https://github.com/bitsandbytes-foundation/bitsandbytes/issues\n\n" |
| ) |
|
|
| def __getattr__(self, name): |
| """Return a dummy function that throws when called, rather than on attribute access""" |
|
|
| def throw_on_call(*args, **kwargs): |
| raise RuntimeError(f"{self.formatted_error}Native code method attempted to call: lib.{name}()") |
|
|
| return throw_on_call |
|
|
| def __getitem__(self, name): |
| return self.__getattr__(name) |
|
|
|
|
| def get_native_library() -> BNBNativeLibrary: |
| """ |
| Load CUDA library XOR CPU, as the latter contains a subset of symbols of the former. |
| """ |
| cuda_specs = get_cuda_specs() |
| binary_path = PACKAGE_DIR / f"libbitsandbytes_cpu{DYNAMIC_LIBRARY_SUFFIX}" |
|
|
| if cuda_specs: |
| cuda_binary_path = get_cuda_bnb_library_path(cuda_specs) |
|
|
| if not cuda_binary_path.exists(): |
| raise RuntimeError(f"Configured {BNB_BACKEND} binary not found at {cuda_binary_path}") |
|
|
| binary_path = cuda_binary_path |
|
|
| if torch._C._has_xpu: |
| binary_path = PACKAGE_DIR / f"libbitsandbytes_xpu{DYNAMIC_LIBRARY_SUFFIX}" |
|
|
| logger.debug(f"Loading bitsandbytes native library from: {binary_path}") |
|
|
| |
| dll = ct.cdll.LoadLibrary(str(binary_path)) |
|
|
| if hasattr(dll, "get_context"): |
| return CudaBNBNativeLibrary(dll) |
|
|
| return BNBNativeLibrary(dll) |
|
|
|
|
| ROCM_GPU_ARCH = get_rocm_gpu_arch() |
| ROCM_WARP_SIZE_64 = True if get_rocm_warpsize() == 64 else False |
|
|
| HIP_ENVIRONMENT = False |
| BNB_BACKEND = "CPU" |
| if torch.version.hip: |
| HIP_ENVIRONMENT = True |
| BNB_BACKEND = "ROCm" |
| elif torch.cuda.is_available(): |
| BNB_BACKEND = "CUDA" |
| elif torch._C._has_xpu: |
| BNB_BACKEND = "XPU" |
|
|
| try: |
| lib = get_native_library() |
| except Exception as e: |
| if BNB_BACKEND in ("CPU", "XPU"): |
| lib = ErrorHandlerMockBNBNativeLibrary("XPU/CPU can run without native library.") |
| else: |
| error_msg = str(e) |
| logger.error( |
| f"bitsandbytes library load error: {error_msg}", |
| exc_info=True, |
| ) |
|
|
| |
| lib = ErrorHandlerMockBNBNativeLibrary(error_msg) |
|
|