Fix setup.py

This commit is contained in:
Daniel Han 2025-07-01 00:05:56 -07:00
commit 0ceeab0821
4 changed files with 161 additions and 146 deletions

View file

@ -6,9 +6,6 @@ requires = [
"packaging>=24.2",
"setuptools>=77.0.3,<80.0.0",
"setuptools-scm>=8.0",
"torch==2.7.0",
"wheel",
"jinja2"
]
build-backend = "setuptools.build_meta"
@ -17,7 +14,7 @@ name = "unsloth"
description = "2-5X faster LLM finetuning"
readme = "README.md"
requires-python = ">=3.9,<3.13"
dynamic = [ "version", "dependencies", "optional-dependencies"]
dynamic = ["version", "dependencies", "optional-dependencies"]
license = { file = "LICENSE" }
keywords = ["ai", "llm",]
authors = [

View file

@ -1,15 +1,21 @@
unsloth_zoo>=2025.6.2
packaging
unsloth_zoo>=2025.6.7
packaging>=24.2
tyro
transformers>=4.51.3,!=4.47.0,!=4.52.0,!=4.52.1,!=4.52.2
transformers>=4.51.3,!=4.47.0,!=4.52.0,!=4.52.1,!=4.52.2,!=4.52.3
datasets>=3.4.1
sentencepiece>=0.2.0
tqdm
tyro
psutil
wheel>=0.42.0
numpy
accelerate>=0.34.1
trl>=0.7.9,!=0.9.0,!=0.9.1,!=0.9.2,!=0.9.3,!=0.15.0
peft>=0.7.1,!=0.11.0
protobuf
huggingface_hub
hf_transfer
pillow
regex
msgspec
jinja2

View file

@ -1,7 +1,9 @@
# Common dependencies
-r common.txt
torch
torch<=2.7.0
torchaudio
torchvision
xformers
xformers<=0.0.30
triton ; platform_system == 'Linux'
triton_windows ; platform_system == 'Windows'

284
setup.py
View file

@ -1,29 +1,51 @@
# Copid and modified based on https://github.com/vllm-project/vllm/blob/main/setup.py
# SPDX-License-Identifier: Apache-2.0
# Copyright 2023-present Daniel Han-Chen & the Unsloth team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Modified from https://github.com/vllm-project/vllm/blob/main/setup.py
import ctypes
import importlib.util
import json
import logging
import os
import re
import subprocess
import sys
from pathlib import Path
from shutil import which
import shutil
import torch
from packaging.version import Version, parse
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext
from setuptools_scm import get_version
from torch.utils.cpp_extension import CUDA_HOME, ROCM_HOME
from setuptools import setup
from setuptools.command.install import install
# This arg is for multi-device
UNSLOTH_TARGET_DEVICE = os.environ.get('UNSLOTH_TARGET_DEVICE', 'cuda')
ROOT_DIR = Path(__file__).parent
UNSLOTH_TARGET_DEVICE = os.environ.get("UNSLOTH_TARGET_DEVICE", "cuda")
IS_COLAB = "COLAB_" not in "".join(os.environ.keys())
UNSLOTH_VERSION = load_module_from_path('ver', os.path.join(ROOT_DIR, 'unsloth', 'version.py'))
# Try importing torch
HAS_TORCH = False
HAS_CUDA = True
HAS_HIP = False
CUDA_HOME = None
ROCM_HOME = None
torch = None
if importlib.util.find_spec("torch") is not None:
try:
import torch
from torch.utils.cpp_extension import CUDA_HOME, ROCM_HOME
HAS_TORCH = True
HAS_CUDA = (torch.version.cuda is not None)
HAS_HIP = (torch.version. hip is not None)
except Exception as e:
print(f"Unsloth: Importing torch failed with error = {str(e)}.")
HAS_TORCH = False
pass
def load_module_from_path(module_name, path):
@ -32,25 +54,10 @@ def load_module_from_path(module_name, path):
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
ROOT_DIR = Path(__file__).parent
pass
# cannot import version directly because it depends on unsloth,
# which is not installed yet
ver = load_module_from_path('ver', os.path.join(ROOT_DIR, 'unsloth', 'version.py'))
def _is_cuda() -> bool:
has_cuda = torch.version.cuda is not None
return UNSLOTH_TARGET_DEVICE == "cuda" and has_cuda
def _is_hip() -> bool:
return (UNSLOTH_TARGET_DEVICE == "cuda"
or UNSLOTH_TARGET_DEVICE == "rocm") and torch.version.hip is not None
def get_nvcc_cuda_version() -> Version:
def get_nvcc_cuda_version():
"""Get the CUDA version from nvcc.
Adapted from https://github.com/NVIDIA/apex/blob/8b7a1ff183741dd8f9b87e7bafd04cfde99cea28/setup.py
@ -62,11 +69,13 @@ def get_nvcc_cuda_version() -> Version:
release_idx = output.index("release") + 1
nvcc_cuda_version = parse(output[release_idx].split(",")[0])
return nvcc_cuda_version
pass
def get_rocm_version():
# Get the Rocm version from the ROCM_HOME/bin/librocm-core.so
# see https://github.com/ROCm/rocm-core/blob/d11f5c20d500f729c393680a01fa902ebf92094b/rocm_version.cpp#L21
import ctypes
try:
librocm_core_file = Path(ROCM_HOME) / "lib" / "librocm-core.so"
if not librocm_core_file.is_file():
@ -90,37 +99,38 @@ def get_rocm_version():
return None
except Exception:
return None
pass
def get_unsloth_version() -> str:
version = ver.__version__
def get_unsloth_version():
version = UNSLOTH_VERSION.__version__
if version is None:
raise RuntimeError("unsloth version not found")
sep = "+" if "+" not in version else "." # dev versions might contain +
if _is_cuda():
if HAS_CUDA:
cuda_version = str(get_nvcc_cuda_version())
cuda_version_str = cuda_version.replace(".", "")[:3]
# skip this for source tarball, required for pypi
if "sdist" not in sys.argv:
version += f"{sep}cu{cuda_version_str}"
elif _is_hip():
elif HAS_HIP:
# Get the Rocm Version
rocm_version = get_rocm_version() or torch.version.hip
if rocm_version:
version += f"{sep}rocm{rocm_version.replace('.', '')[:3]}"
else:
raise RuntimeError("Unknown runtime environment")
return version
pass
def get_requirements() -> list[str]:
def get_requirements():
"""Get Python package dependencies from requirements.txt."""
requirements_dir = ROOT_DIR / "requirements"
def _read_requirements(filename: str) -> list[str]:
def _read_requirements(filename):
with open(requirements_dir / filename) as f:
requirements = f.read().strip().split("\n")
resolved_requirements = []
@ -131,110 +141,27 @@ def get_requirements() -> list[str]:
"#") and line.strip() != "":
resolved_requirements.append(line)
return resolved_requirements
if _is_cuda():
pass
if HAS_CUDA:
requirements = _read_requirements("cuda.txt")
elif _is_hip():
elif HAS_HIP:
requirements = _read_requirements("rocm.txt")
else:
requirements = _read_requirements("common.txt")
raise ValueError(
"Unsupported platform, please use CUDA, ROCm, "
)
return requirements
pass
INSTINCT_ARCH=("gfx942", "gfx90a")
RADEON_ARCH=("gfx1100", "gfx1101", "gfx1102", "gfx1200", "gfx1201")
class RocmExtraInstallCommand(install):
def run(self):
if os.path.exists('thirdparties'):
shutil.rmtree('thirdparties')
os.mkdir('thirdparties')
os.chdir('thirdparties')
# Extract ROCm GPU arch from environment variable. If unset, then detect ROCm arch from rocminfo
# Refer to https://github.com/bitsandbytes-foundation/bitsandbytes/blob/1abd5e781013a085f86586b30a248dc769909668/bitsandbytes/cuda_specs.py#L81
# TODO(billishyahao): need to triage rocminfo unavailable observation from https://github.com/bitsandbytes-foundation/bitsandbytes/issues/1444
rocm_arch = os.environ.get('ROCM_ARCH', None)
if rocm_arch is None:
try:
result = subprocess.run(["rocminfo"], capture_output=True, text=True)
match = re.search(r"Name:\s+gfx([a-zA-Z\d]+)", result.stdout)
if match:
rocm_arch = f"gfx{match.group(1)}"
print(f"Automatically detected ROCm GPU architecture: {rocm_arch}")
else:
print("Skipping ROCm extra install, cannot detect ROCm arch automatically...")
install.run(self)
return
except Exception as e:
print("Could not detect ROCm GPU architecture: {e}")
if torch.cuda.is_available():
print("ROCm GPU architecture detection failed despite ROCm being available...")
install.run(self)
return
# flash-attention
# MI3xx has both CK backend and Triton backend.
import importlib
if importlib.util.find_spec("flash_attn") is None:
print("Installing flash-attention...")
if rocm_arch in INSTINCT_ARCH:
subprocess.check_call(['git', 'clone', '--recursive', 'https://github.com/ROCm/flash-attention.git'])
os.chdir('flash-attention')
num_jobs = os.cpu_count() - 1
subprocess.check_call(['pip', 'install', '-v', '.', f'MAX_JOBS={num_jobs}'], shell=True)
os.chdir('..')
# Only Triton backend supports Radeon GPUs
elif rocm_arch in RADEON_ARCH:
subprocess.check_call(['git', 'clone', '--recursive', 'https://github.com/ROCm/flash-attention.git'])
os.chdir('flash-attention')
subprocess.check_call(['git', 'checkout', 'main_perf'])
subprocess.check_call(['FLASH_ATTENTION_TRITON_AMD_ENABLE="TRUE"', 'python', 'setup.py', 'install', ], shell=True)
os.chdir('..')
# Comment out the following if you need xformers installed.
# # only install xformers in Instinct GPUs
# if importlib.util.find_spec("xformers") is None:
# print("Installing xformers...")
# if rocm_arch in INSTINCT_ARCH:
# subprocess.check_call(['git', 'clone', 'https://github.com/ROCm/xformers.git'])
# os.chdir('xformers')
# subprocess.check_call(['git', 'submodule', 'update', '--init', '--recursive'])
# os.environ['PYTORCH_ROCM_ARCH'] = rocm_arch
# subprocess.check_call(['python', 'setup.py', 'install'])
# os.chdir('..')
# bitsandbytes
if importlib.util.find_spec("bitsandbytes") is None:
print("Installing bitsandbytes...")
subprocess.check_call(['git', 'clone', '--recurse-submodules', 'https://github.com/ROCm/bitsandbytes'])
os.chdir('bitsandbytes')
subprocess.check_call(['git', 'checkout', 'rocm_enabled_multi_backend'])
subprocess.check_call(['pip', 'install', '-r', 'requirements-dev.txt'])
subprocess.check_call(['cmake', '-DCOMPUTE_BACKEND=hip', '-S', '.']) # Add -DBNB_ROCM_ARCH if needed
subprocess.check_call(['make'])
subprocess.check_call(['pip', 'install', '.'])
os.chdir('..')
os.chdir('..')
# Continue with regular install
install.run(self)
package_data = {
"unsloth": [
"py.typed",
]
}
extras_require = {
extras_requires = {
"triton" : [
"triton-windows ; platform_system == 'Windows'",
],
@ -829,16 +756,99 @@ extras_require = {
cmdclass = {}
if _is_hip():
if HAS_HIP:
class RocmExtraInstallCommand(install):
def run(self):
import re
import shutil
INSTINCT_ARCH = ("gfx942", "gfx90a")
RADEON_ARCH = ("gfx1100", "gfx1101", "gfx1102", "gfx1200", "gfx1201")
if os.path.exists('thirdparties'):
shutil.rmtree('thirdparties')
os.mkdir('thirdparties')
os.chdir('thirdparties')
# Extract ROCm GPU arch from environment variable. If unset, then detect ROCm arch from rocminfo
# Refer to https://github.com/bitsandbytes-foundation/bitsandbytes/blob/1abd5e781013a085f86586b30a248dc769909668/bitsandbytes/cuda_specs.py#L81
# TODO(billishyahao): need to triage rocminfo unavailable observation from https://github.com/bitsandbytes-foundation/bitsandbytes/issues/1444
rocm_arch = os.environ.get('ROCM_ARCH', None)
if rocm_arch is None:
try:
result = subprocess.run(["rocminfo"], capture_output=True, text=True)
match = re.search(r"Name:\s+gfx([a-zA-Z\d]+)", result.stdout)
if match:
rocm_arch = f"gfx{match.group(1)}"
print(f"Automatically detected ROCm GPU architecture: {rocm_arch}")
else:
print("Skipping ROCm extra install, cannot detect ROCm arch automatically...")
install.run(self)
return
except Exception as e:
print("Could not detect ROCm GPU architecture: {e}")
if torch.cuda.is_available():
print("ROCm GPU architecture detection failed despite ROCm being available...")
install.run(self)
return
# flash-attention
# MI3xx has both CK backend and Triton backend.
if importlib.util.find_spec("flash_attn") is None:
print("Installing flash-attention...")
if rocm_arch in INSTINCT_ARCH:
subprocess.check_call(['git', 'clone', '--recursive', 'https://github.com/ROCm/flash-attention.git'])
os.chdir('flash-attention')
num_jobs = os.cpu_count() - 1
subprocess.check_call(['pip', 'install', '-v', '.', f'MAX_JOBS={num_jobs}'], shell=True)
os.chdir('..')
# Only Triton backend supports Radeon GPUs
elif rocm_arch in RADEON_ARCH:
subprocess.check_call(['git', 'clone', '--recursive', 'https://github.com/ROCm/flash-attention.git'])
os.chdir('flash-attention')
subprocess.check_call(['git', 'checkout', 'main_perf'])
subprocess.check_call(['FLASH_ATTENTION_TRITON_AMD_ENABLE="TRUE"', 'python', 'setup.py', 'install', ], shell=True)
os.chdir('..')
# Comment out the following if you need xformers installed.
# # only install xformers in Instinct GPUs
# if importlib.util.find_spec("xformers") is None:
# print("Installing xformers...")
# if rocm_arch in INSTINCT_ARCH:
# subprocess.check_call(['git', 'clone', 'https://github.com/ROCm/xformers.git'])
# os.chdir('xformers')
# subprocess.check_call(['git', 'submodule', 'update', '--init', '--recursive'])
# os.environ['PYTORCH_ROCM_ARCH'] = rocm_arch
# subprocess.check_call(['python', 'setup.py', 'install'])
# os.chdir('..')
# bitsandbytes
if importlib.util.find_spec("bitsandbytes") is None:
print("Installing bitsandbytes...")
subprocess.check_call(['git', 'clone', '--recurse-submodules', 'https://github.com/ROCm/bitsandbytes'])
os.chdir('bitsandbytes')
subprocess.check_call(['git', 'checkout', 'rocm_enabled_multi_backend'])
subprocess.check_call(['pip', 'install', '-r', 'requirements-dev.txt'])
subprocess.check_call(['cmake', '-DCOMPUTE_BACKEND=hip', '-S', '.']) # Add -DBNB_ROCM_ARCH if needed
subprocess.check_call(['make'])
subprocess.check_call(['pip', 'install', '.'])
os.chdir('..')
os.chdir('..')
# Continue with regular install
install.run(self)
pass
pass
cmdclass = {
'install': RocmExtraInstallCommand
}
pass
setup(
# static metadata should rather go in pyproject.toml
version=get_unsloth_version(),
install_requires=get_requirements(),
extras_require=extras_require,
cmdclass=cmdclass,
package_data=package_data,
version = get_unsloth_version(),
install_requires = get_requirements(),
extras_require = extras_requires,
cmdclass = cmdclass,
package_data = package_data,
)