2022-07-28 04:16:04 +00:00
"""
2022-08-01 16:32:47 +00:00
extract factors the build is dependent on :
2022-08-02 14:42:27 +00:00
[ X ] compute capability
2022-08-01 16:32:47 +00:00
[ ] TODO : Q - What if we have multiple GPUs of different makes ?
2022-07-28 04:16:04 +00:00
- CUDA version
- Software :
- CPU - only : only CPU quantization functions ( no optimizer , no matrix multipl )
- CuBLAS - LT : full - build 8 - bit optimizer
- no CuBLAS - LT : no 8 - bit matrix multiplication ( ` nomatmul ` )
evaluation :
- if paths faulty , return meaningful error
- else :
- determine CUDA version
- determine capabilities
- based on that set the default path
"""
2023-01-02 11:31:43 +00:00
import ctypes as ct
2022-10-27 22:04:49 +00:00
import os
2023-01-02 11:31:43 +00:00
import errno
2022-11-01 01:04:49 +00:00
import torch
2023-01-03 13:23:34 +00:00
from warnings import warn
2023-04-11 19:10:20 +00:00
from itertools import product
2022-07-28 04:16:04 +00:00
2023-01-02 11:31:43 +00:00
from pathlib import Path
from typing import Set , Union
from . env_vars import get_potentially_lib_path_containing_env_vars
2023-04-11 19:10:20 +00:00
# these are the most common libs names
# libcudart.so is missing by default for a conda install with PyTorch 2.0 and instead
# we have libcudart.so.11.0 which causes a lot of errors before
# not sure if libcudart.so.12.0 exists in pytorch installs, but it does not hurt
CUDA_RUNTIME_LIBS : list = [ " libcudart.so " , ' libcudart.so.11.0 ' , ' libcudart.so.12.0 ' ]
# this is a order list of backup paths to search CUDA in, if it cannot be found in the main environmental paths
backup_paths = [ ]
backup_paths . append ( ' $CONDA_PREFIX/lib/libcudart.so.11.0 ' )
2023-01-02 11:31:43 +00:00
class CUDASetup :
_instance = None
def __init__ ( self ) :
raise RuntimeError ( " Call get_instance() instead " )
def generate_instructions ( self ) :
2023-02-04 22:52:04 +00:00
if getattr ( self , ' error ' , False ) : return
print ( self . error )
self . error = True
2023-07-14 06:58:41 +00:00
if not self . cuda_available :
self . add_log_entry ( ' CUDA SETUP: Problem: The main issue seems to be that the main CUDA library was not detected or CUDA not installed. ' )
2023-01-02 11:31:43 +00:00
self . add_log_entry ( ' CUDA SETUP: Solution 1): Your paths are probably not up-to-date. You can update them via: sudo ldconfig. ' )
self . add_log_entry ( ' CUDA SETUP: Solution 2): If you do not have sudo rights, you can do the following: ' )
self . add_log_entry ( ' CUDA SETUP: Solution 2a): Find the cuda library via: find / -name libcuda.so 2>/dev/null ' )
self . add_log_entry ( ' CUDA SETUP: Solution 2b): Once the library is found add it to the LD_LIBRARY_PATH: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:FOUND_PATH_FROM_2a ' )
self . add_log_entry ( ' CUDA SETUP: Solution 2c): For a permanent solution add the export from 2b into your .bashrc file, located at ~/.bashrc ' )
2023-07-14 06:58:41 +00:00
self . add_log_entry ( ' CUDA SETUP: Solution 3): For a missing CUDA runtime library (libcudart.so), use `find / -name libcudart.so* and follow with step (2b) ' )
2023-01-02 11:31:43 +00:00
return
if self . cudart_path is None :
self . add_log_entry ( ' CUDA SETUP: Problem: The main issue seems to be that the main CUDA runtime library was not detected. ' )
self . add_log_entry ( ' CUDA SETUP: Solution 1: To solve the issue the libcudart.so location needs to be added to the LD_LIBRARY_PATH variable ' )
self . add_log_entry ( ' CUDA SETUP: Solution 1a): Find the cuda runtime library via: find / -name libcudart.so 2>/dev/null ' )
self . add_log_entry ( ' CUDA SETUP: Solution 1b): Once the library is found add it to the LD_LIBRARY_PATH: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:FOUND_PATH_FROM_1a ' )
self . add_log_entry ( ' CUDA SETUP: Solution 1c): For a permanent solution add the export from 1b into your .bashrc file, located at ~/.bashrc ' )
self . add_log_entry ( ' CUDA SETUP: Solution 2: If no library was found in step 1a) you need to install CUDA. ' )
self . add_log_entry ( ' CUDA SETUP: Solution 2a): Download CUDA install script: wget https://github.com/TimDettmers/bitsandbytes/blob/main/cuda_install.sh ' )
self . add_log_entry ( ' CUDA SETUP: Solution 2b): Install desired CUDA version to desired location. The syntax is bash cuda_install.sh CUDA_VERSION PATH_TO_INSTALL_INTO. ' )
self . add_log_entry ( ' CUDA SETUP: Solution 2b): For example, " bash cuda_install.sh 113 ~/local/ " will download CUDA 11.3 and install into the folder ~/local ' )
return
make_cmd = f ' CUDA_VERSION= { self . cuda_version_string } '
if len ( self . cuda_version_string ) < 3 :
make_cmd + = ' make cuda92 '
elif self . cuda_version_string == ' 110 ' :
make_cmd + = ' make cuda110 '
elif self . cuda_version_string [ : 2 ] == ' 11 ' and int ( self . cuda_version_string [ 2 ] ) > 0 :
make_cmd + = ' make cuda11x '
2023-01-03 14:07:35 +00:00
elif self . cuda_version_string == ' 100 ' :
self . add_log_entry ( ' CUDA SETUP: CUDA 10.0 not supported. Please use a different CUDA version. ' )
self . add_log_entry ( ' CUDA SETUP: Before you try again running bitsandbytes, make sure old CUDA 10.0 versions are uninstalled and removed from $LD_LIBRARY_PATH variables. ' )
return
2023-01-02 11:31:43 +00:00
2023-01-02 11:47:09 +00:00
has_cublaslt = is_cublasLt_compatible ( self . cc )
2023-01-02 11:31:43 +00:00
if not has_cublaslt :
make_cmd + = ' _nomatmul '
self . add_log_entry ( ' CUDA SETUP: Something unexpected happened. Please compile from source: ' )
self . add_log_entry ( ' git clone git@github.com:TimDettmers/bitsandbytes.git ' )
self . add_log_entry ( ' cd bitsandbytes ' )
self . add_log_entry ( make_cmd )
self . add_log_entry ( ' python setup.py install ' )
def initialize ( self ) :
2023-02-02 04:27:01 +00:00
if not getattr ( self , ' initialized ' , False ) :
self . has_printed = False
self . lib = None
self . initialized = False
2023-02-04 22:52:04 +00:00
self . error = False
2023-01-02 11:31:43 +00:00
2023-07-14 06:58:41 +00:00
def manual_override ( self ) :
if torch . cuda . is_available ( ) :
2023-07-14 19:50:59 +00:00
if ' CUDA_VERSION ' in os . environ :
if len ( os . environ [ ' CUDA_VERSION ' ] ) > 0 :
warn ( ( f ' \n \n { " = " * 80 } \n '
' WARNING: Manual override via CUDA_VERSION env variable detected! \n '
' CUDA_VERSION=XXX can be used to load a bitsandbytes version that is different from the PyTorch CUDA version. \n '
' If this was unintended set the CUDA_VERSION variable to an empty string: export CUDA_VERSION= \n '
' If you use the manual override make sure the right libcudart.so is in your LD_LIBRARY_PATH \n '
' For example by adding the following to your .bashrc: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<path_to_cuda_dir/lib64 \n '
f ' Loading CUDA version: CUDA_VERSION= { os . environ [ " CUDA_VERSION " ] } '
f ' \n { " = " * 80 } \n \n ' ) )
2023-07-14 06:58:41 +00:00
self . binary_name = self . binary_name [ : - 6 ] + f ' { os . environ [ " CUDA_VERSION " ] } .so '
2023-01-02 11:31:43 +00:00
def run_cuda_setup ( self ) :
self . initialized = True
self . cuda_setup_log = [ ]
2023-07-14 06:58:41 +00:00
binary_name , cudart_path , cc , cuda_version_string = evaluate_cuda_setup ( )
2023-01-02 11:31:43 +00:00
self . cudart_path = cudart_path
2023-07-14 06:58:41 +00:00
self . cuda_available = torch . cuda . is_available ( )
2023-01-02 11:31:43 +00:00
self . cc = cc
self . cuda_version_string = cuda_version_string
2023-07-14 06:58:41 +00:00
self . binary_name = binary_name
self . manual_override ( )
2023-01-02 11:31:43 +00:00
package_dir = Path ( __file__ ) . parent . parent
2023-07-14 06:58:41 +00:00
binary_path = package_dir / self . binary_name
2023-04-11 19:10:20 +00:00
2023-01-02 11:31:43 +00:00
try :
if not binary_path . exists ( ) :
self . add_log_entry ( f " CUDA SETUP: Required library version not found: { binary_name } . Maybe you need to compile it from source? " )
2023-01-02 11:47:09 +00:00
legacy_binary_name = " libbitsandbytes_cpu.so "
2023-01-02 11:31:43 +00:00
self . add_log_entry ( f " CUDA SETUP: Defaulting to { legacy_binary_name } ... " )
binary_path = package_dir / legacy_binary_name
2023-02-02 04:27:01 +00:00
if not binary_path . exists ( ) or torch . cuda . is_available ( ) :
2023-01-02 11:31:43 +00:00
self . add_log_entry ( ' ' )
self . add_log_entry ( ' = ' * 48 + ' ERROR ' + ' = ' * 37 )
self . add_log_entry ( ' CUDA SETUP: CUDA detection failed! Possible reasons: ' )
2023-07-14 06:58:41 +00:00
self . add_log_entry ( ' 1. You need to manually override the PyTorch CUDA version. Please see: '
' " https://github.com/TimDettmers/bitsandbytes/blob/main/how_to_use_nonpytorch_cuda.md ' )
self . add_log_entry ( ' 2. CUDA driver not installed ' )
self . add_log_entry ( ' 3. CUDA not installed ' )
self . add_log_entry ( ' 4. You have multiple conflicting CUDA libraries ' )
self . add_log_entry ( ' 5. Required library not pre-compiled for this bitsandbytes release! ' )
2023-01-02 11:31:43 +00:00
self . add_log_entry ( ' CUDA SETUP: If you compiled from source, try again with `make CUDA_VERSION=DETECTED_CUDA_VERSION` for example, `make CUDA_VERSION=113`. ' )
2023-02-02 04:27:01 +00:00
self . add_log_entry ( ' CUDA SETUP: The CUDA version for the compile might depend on your conda install. Inspect CUDA version via `conda list | grep cuda`. ' )
2023-01-02 11:31:43 +00:00
self . add_log_entry ( ' = ' * 80 )
self . add_log_entry ( ' ' )
self . generate_instructions ( )
raise Exception ( ' CUDA SETUP: Setup Failed! ' )
self . lib = ct . cdll . LoadLibrary ( binary_path )
else :
self . add_log_entry ( f " CUDA SETUP: Loading binary { binary_path } ... " )
self . lib = ct . cdll . LoadLibrary ( binary_path )
except Exception as ex :
self . add_log_entry ( str ( ex ) )
def add_log_entry ( self , msg , is_warning = False ) :
self . cuda_setup_log . append ( ( msg , is_warning ) )
def print_log_stack ( self ) :
for msg , is_warning in self . cuda_setup_log :
if is_warning :
warn ( msg )
else :
print ( msg )
@classmethod
def get_instance ( cls ) :
if cls . _instance is None :
cls . _instance = cls . __new__ ( cls )
cls . _instance . initialize ( )
return cls . _instance
2023-01-02 11:47:09 +00:00
def is_cublasLt_compatible ( cc ) :
has_cublaslt = False
if cc is not None :
cc_major , cc_minor = cc . split ( ' . ' )
if int ( cc_major ) < 7 or ( int ( cc_major ) == 7 and int ( cc_minor ) < 5 ) :
2023-07-14 04:41:43 +00:00
CUDASetup . get_instance ( ) . add_log_entry ( " WARNING: Compute capability < 7.5 detected! Only slow 8-bit matmul is supported for your GPU! \
If you run into issues with 8 - bit matmul , you can try 4 - bit quantization : https : / / huggingface . co / blog / 4 bit - transformers - bitsandbytes " , is_warning=True)
2023-01-02 11:47:09 +00:00
else :
has_cublaslt = True
return has_cublaslt
2023-01-02 11:31:43 +00:00
def extract_candidate_paths ( paths_list_candidate : str ) - > Set [ Path ] :
return { Path ( ld_path ) for ld_path in paths_list_candidate . split ( " : " ) if ld_path }
def remove_non_existent_dirs ( candidate_paths : Set [ Path ] ) - > Set [ Path ] :
existent_directories : Set [ Path ] = set ( )
for path in candidate_paths :
try :
if path . exists ( ) :
existent_directories . add ( path )
except OSError as exc :
if exc . errno != errno . ENAMETOOLONG :
raise exc
non_existent_directories : Set [ Path ] = candidate_paths - existent_directories
if non_existent_directories :
2023-07-14 19:50:59 +00:00
CUDASetup . get_instance ( ) . add_log_entry ( " The following directories listed in your path were found to "
f " be non-existent: { non_existent_directories } " , is_warning = False )
2023-01-02 11:31:43 +00:00
return existent_directories
def get_cuda_runtime_lib_paths ( candidate_paths : Set [ Path ] ) - > Set [ Path ] :
2023-04-11 19:10:20 +00:00
paths = set ( )
for libname in CUDA_RUNTIME_LIBS :
for path in candidate_paths :
if ( path / libname ) . is_file ( ) :
paths . add ( path / libname )
return paths
2023-01-02 11:31:43 +00:00
def resolve_paths_list ( paths_list_candidate : str ) - > Set [ Path ] :
"""
Searches a given environmental var for the CUDA runtime library ,
i . e . ` libcudart . so ` .
"""
return remove_non_existent_dirs ( extract_candidate_paths ( paths_list_candidate ) )
def find_cuda_lib_in ( paths_list_candidate : str ) - > Set [ Path ] :
return get_cuda_runtime_lib_paths (
resolve_paths_list ( paths_list_candidate )
)
def warn_in_case_of_duplicates ( results_paths : Set [ Path ] ) - > None :
if len ( results_paths ) > 1 :
warning_msg = (
2023-04-11 23:14:29 +00:00
f " Found duplicate { CUDA_RUNTIME_LIBS } files: { results_paths } .. "
2023-07-14 06:58:41 +00:00
" We select the PyTorch default libcudart.so, which is {torch.version.cuda} , "
" but this might missmatch with the CUDA version that is needed for bitsandbytes. "
2023-07-14 19:50:59 +00:00
" To override this behavior set the CUDA_VERSION=<version string, e.g. 122> environmental variable "
" For example, if you want to use the CUDA version 122 "
" CUDA_VERSION=122 python ... "
" OR set the environmental variable in your .bashrc: export CUDA_VERSION=122 "
" In the case of a manual override, make sure you set the LD_LIBRARY_PATH, e.g. "
" export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda-11.2 " )
2023-01-02 11:31:43 +00:00
CUDASetup . get_instance ( ) . add_log_entry ( warning_msg , is_warning = True )
def determine_cuda_runtime_lib_path ( ) - > Union [ Path , None ] :
"""
Searches for a cuda installations , in the following order of priority :
1. active conda env
2. LD_LIBRARY_PATH
3. any other env vars , while ignoring those that
- are known to be unrelated ( see ` bnb . cuda_setup . env_vars . to_be_ignored ` )
- don ' t contain the path separator `/`
If multiple libraries are found in part 3 , we optimistically try one ,
while giving a warning message .
"""
candidate_env_vars = get_potentially_lib_path_containing_env_vars ( )
2023-07-14 06:58:41 +00:00
cuda_runtime_libs = set ( )
2023-01-02 11:31:43 +00:00
if " CONDA_PREFIX " in candidate_env_vars :
conda_libs_path = Path ( candidate_env_vars [ " CONDA_PREFIX " ] ) / " lib "
conda_cuda_libs = find_cuda_lib_in ( str ( conda_libs_path ) )
warn_in_case_of_duplicates ( conda_cuda_libs )
if conda_cuda_libs :
2023-07-14 06:58:41 +00:00
cuda_runtime_libs . update ( conda_cuda_libs )
2023-01-02 11:31:43 +00:00
CUDASetup . get_instance ( ) . add_log_entry ( f ' { candidate_env_vars [ " CONDA_PREFIX " ] } did not contain '
2023-04-11 23:14:29 +00:00
f ' { CUDA_RUNTIME_LIBS } as expected! Searching further paths... ' , is_warning = True )
2023-01-02 11:31:43 +00:00
if " LD_LIBRARY_PATH " in candidate_env_vars :
lib_ld_cuda_libs = find_cuda_lib_in ( candidate_env_vars [ " LD_LIBRARY_PATH " ] )
if lib_ld_cuda_libs :
2023-07-14 06:58:41 +00:00
cuda_runtime_libs . update ( lib_ld_cuda_libs )
2023-01-02 11:31:43 +00:00
warn_in_case_of_duplicates ( lib_ld_cuda_libs )
CUDASetup . get_instance ( ) . add_log_entry ( f ' { candidate_env_vars [ " LD_LIBRARY_PATH " ] } did not contain '
2023-04-11 23:14:29 +00:00
f ' { CUDA_RUNTIME_LIBS } as expected! Searching further paths... ' , is_warning = True )
2023-01-02 11:31:43 +00:00
remaining_candidate_env_vars = {
env_var : value for env_var , value in candidate_env_vars . items ( )
if env_var not in { " CONDA_PREFIX " , " LD_LIBRARY_PATH " }
}
cuda_runtime_libs = set ( )
for env_var , value in remaining_candidate_env_vars . items ( ) :
cuda_runtime_libs . update ( find_cuda_lib_in ( value ) )
if len ( cuda_runtime_libs ) == 0 :
2023-04-11 19:10:20 +00:00
CUDASetup . get_instance ( ) . add_log_entry ( ' CUDA_SETUP: WARNING! libcudart.so not found in any environmental path. Searching in backup paths... ' )
2023-01-02 11:31:43 +00:00
cuda_runtime_libs . update ( find_cuda_lib_in ( ' /usr/local/cuda/lib64 ' ) )
warn_in_case_of_duplicates ( cuda_runtime_libs )
2022-08-01 10:31:48 +00:00
2023-07-14 19:50:59 +00:00
cuda_setup = CUDASetup . get_instance ( )
cuda_setup . add_log_entry ( f ' DEBUG: Possible options found for libcudart.so: { cuda_runtime_libs } ' )
2023-07-14 06:58:41 +00:00
2023-01-02 11:31:43 +00:00
return next ( iter ( cuda_runtime_libs ) ) if cuda_runtime_libs else None
2022-10-27 11:15:21 +00:00
2022-08-01 00:47:44 +00:00
2022-11-01 01:04:49 +00:00
# https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART____VERSION.html#group__CUDART____VERSION
2023-07-14 06:58:41 +00:00
def get_cuda_version ( ) :
2023-04-28 02:18:54 +00:00
major , minor = map ( int , torch . version . cuda . split ( " . " ) )
2022-08-05 14:13:24 +00:00
2022-08-10 03:02:47 +00:00
if major < 11 :
2022-11-01 08:53:47 +00:00
CUDASetup . get_instance ( ) . add_log_entry ( ' CUDA SETUP: CUDA version lower than 11 are currently not supported for LLM.int8(). You will be only to use 8-bit optimizers and quantization routines!! ' )
2022-08-10 03:02:47 +00:00
2022-08-05 14:13:24 +00:00
return f ' { major } { minor } '
2023-07-14 06:58:41 +00:00
def get_compute_capabilities ( ) :
2022-08-01 00:47:44 +00:00
ccs = [ ]
2023-04-28 02:18:54 +00:00
for i in range ( torch . cuda . device_count ( ) ) :
cc_major , cc_minor = torch . cuda . get_device_capability ( torch . cuda . device ( i ) )
ccs . append ( f " { cc_major } . { cc_minor } " )
2022-07-28 04:16:04 +00:00
2022-08-04 16:16:00 +00:00
return ccs
2022-07-28 04:16:04 +00:00
def evaluate_cuda_setup ( ) :
2023-07-14 19:50:59 +00:00
cuda_setup = CUDASetup . get_instance ( )
2022-10-27 22:04:49 +00:00
if ' BITSANDBYTES_NOWELCOME ' not in os . environ or str ( os . environ [ ' BITSANDBYTES_NOWELCOME ' ] ) == ' 0 ' :
2023-07-14 19:50:59 +00:00
cuda_setup . add_log_entry ( ' ' )
cuda_setup . add_log_entry ( ' = ' * 35 + ' BUG REPORT ' + ' = ' * 35 )
cuda_setup . add_log_entry ( ( ' Welcome to bitsandbytes. For bug reports, please run \n \n python -m bitsandbytes \n \n ' ) ,
2023-04-11 20:47:10 +00:00
( ' and submit this information together with your error trace to: https://github.com/TimDettmers/bitsandbytes/issues ' ) )
2023-07-14 19:50:59 +00:00
cuda_setup . add_log_entry ( ' = ' * 80 )
2023-04-05 00:28:41 +00:00
if not torch . cuda . is_available ( ) : return ' libbitsandbytes_cpu.so ' , None , None , None , None
2022-10-24 18:54:25 +00:00
2022-08-05 14:13:24 +00:00
cudart_path = determine_cuda_runtime_lib_path ( )
2023-07-14 06:58:41 +00:00
ccs = get_compute_capabilities ( )
ccs . sort ( )
cc = ccs [ - 1 ] # we take the highest capability
cuda_version_string = get_cuda_version ( )
2022-08-01 00:47:44 +00:00
2023-07-14 06:58:41 +00:00
cuda_setup . add_log_entry ( f " CUDA SETUP: PyTorch settings found: CUDA_VERSION= { cuda_version_string } , Highest Compute Capability: { cc } . " )
cuda_setup . add_log_entry ( f " CUDA SETUP: To manually override the PyTorch CUDA version please see: "
" https://github.com/TimDettmers/bitsandbytes/blob/main/how_to_use_nonpytorch_cuda.md " )
2022-08-01 00:47:44 +00:00
2022-08-03 04:26:50 +00:00
# 7.5 is the minimum CC vor cublaslt
2023-01-02 11:47:09 +00:00
has_cublaslt = is_cublasLt_compatible ( cc )
2022-08-01 00:47:44 +00:00
2022-08-01 10:31:48 +00:00
# TODO:
2022-08-02 02:43:09 +00:00
# (1) CUDA missing cases (no CUDA installed by CUDA driver (nvidia-smi accessible)
2022-08-01 00:47:44 +00:00
# (2) Multiple CUDA versions installed
2022-08-02 02:43:09 +00:00
# we use ls -l instead of nvcc to determine the cuda version
# since most installations will have the libcudart.so installed, but not the compiler
2022-08-01 00:47:44 +00:00
2023-07-14 06:58:41 +00:00
if has_cublaslt :
2022-11-01 01:04:49 +00:00
binary_name = f " libbitsandbytes_cuda { cuda_version_string } .so "
else :
2022-08-03 04:26:50 +00:00
" if not has_cublaslt (CC < 7.5), then we have to choose _nocublaslt.so "
2022-11-01 01:04:49 +00:00
binary_name = f " libbitsandbytes_cuda { cuda_version_string } _nocublaslt.so "
2022-08-01 00:47:44 +00:00
2023-07-14 06:58:41 +00:00
return binary_name , cudart_path , cc , cuda_version_string