unsloth/unsloth/import_fixes.py
Daniel Han a665c9b57d Also patch accelerate's is_wandb_available for trl callbacks path (#4148)
trl/trainer/callbacks.py imports is_wandb_available from
accelerate.utils, not from transformers. The original fix in #4147
only patched the transformers version, so `from trl import GRPOTrainer`
still crashed via the callbacks.py -> accelerate -> wandb path.

Must patch both the source module (accelerate.utils.imports) AND the
re-export namespace (accelerate.utils) since Python's
`from accelerate.utils import X` reads from the latter, which holds
its own cached reference.
2026-03-03 08:28:55 -08:00

1823 lines
64 KiB
Python

# 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.
import os
import importlib.abc
import importlib.machinery
import importlib.util
from pathlib import Path
from importlib.metadata import version as importlib_version
from packaging.version import Version as TrueVersion
import re
import logging
import textwrap
import warnings
import sys
import functools
# We cannot do from unsloth_zoo.log import logger since FBGEMM might cause seg faults.
UNSLOTH_ENABLE_LOGGING = os.environ.get("UNSLOTH_ENABLE_LOGGING", "0") in (
"1",
"True",
"true",
)
logger = logging.getLogger(__name__)
if UNSLOTH_ENABLE_LOGGING:
logging.basicConfig(
level = logging.INFO, format = "[%(name)s|%(levelname)s]%(message)s"
)
logger.setLevel(logging.INFO)
else:
logging.basicConfig(
level = logging.WARNING, format = "[%(name)s|%(levelname)s]%(message)s"
)
logger.setLevel(logging.WARNING)
_AMDGPU_IDS_MISSING_TEXT = "amdgpu.ids: No such file or directory"
def Version(version):
try:
new_version = str(version)
new_version = re.match(r"[0-9\.]{1,}", new_version)
if new_version is None:
raise Exception(str(e))
new_version = new_version.group(0).rstrip(".")
if new_version != version:
new_version += ".1" # Add .1 for dev / alpha / beta / rc
return TrueVersion(new_version)
except:
from inspect import getframeinfo, stack
caller = getframeinfo(stack()[1][0])
raise RuntimeError(
f"Unsloth: Could not get version for `{version}`\n"
f"File name = [{caller.filename}] Line number = [{caller.lineno}]"
)
# Ignore logging messages
class HideLoggingMessage(logging.Filter):
__slots__ = ("text",)
def __init__(self, text):
self.text = text
def filter(self, x):
return not (self.text in x.getMessage())
class HidePrintMessage:
def __init__(self, original_stream):
self._original_stream = original_stream
self._hidden_texts = []
def add_filter(self, text):
self._hidden_texts.append(text)
def write(self, message):
if not any(text in message for text in self._hidden_texts):
self._original_stream.write(message)
def flush(self):
self._original_stream.flush()
def __getattr__(self, name):
return getattr(self._original_stream, name)
import contextlib
import ctypes
try:
_libc = ctypes.CDLL(None)
except Exception:
_libc = None
@contextlib.contextmanager
def suppress_cuda_printf():
"""Suppress CUDA device-side printf by redirecting stdout/stderr fds to /dev/null.
CUDA device printf (eg CUTLASS "Arch conditional MMA" errors on Blackwell)
writes to stdout fd 1 at the C level, bypassing Python sys.stdout entirely.
The existing HidePrintMessage filter on sys.stderr cannot catch these since
they go to a different fd at a different layer. This context manager redirects
both fd 1 and fd 2 at the OS level, syncs CUDA, then restores them.
"""
sys.stdout.flush()
sys.stderr.flush()
saved_fds = {}
try:
for fd in (1, 2):
saved_fds[fd] = os.dup(fd)
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, fd)
os.close(devnull)
yield
finally:
try:
import torch
if torch.cuda.is_available():
torch.cuda.synchronize()
except Exception:
pass
if _libc is not None:
try:
_libc.fflush(None)
except Exception:
pass
for fd, saved in saved_fds.items():
os.dup2(saved, fd)
os.close(saved)
if not UNSLOTH_ENABLE_LOGGING:
import sys
# Apply to stderr for FBGEMM and CUTLASS errors
sys.stderr = HidePrintMessage(sys.stderr)
# https://github.com/pytorch/FBGEMM/blob/d99cd96490ec4aabac2ee95b1e76ea4dcfcfa628/fbgemm_gpu/experimental/gemm/triton_gemm/utils.py#L43-L52
sys.stderr.add_filter("TMA benchmarks will be running")
# CUTLASS/FBGEMM MMA instruction error on SM90 vs SM100 (Blackwell) GPUs
# https://github.com/NVIDIA/cutlass/blob/main/include/cutlass/gemm/kernel/sm90_gemm_tma_warpspecialized.hpp
sys.stderr.add_filter("Arch conditional MMA instruction used without targeting")
# CUTLASS arch conditional errors for various architectures
sys.stderr.add_filter("CUTE_INVALID_CONTROL_PATH")
# CUTLASS TMA-related errors when not targeting correct architecture
sys.stderr.add_filter("Trying to use tma without CUTE_ARCH_TMA")
# Skipping import of cpp extensions due to incompatible torch version 2.9.0+cu128 for torchao version 0.15.0
logging.getLogger("torchao").setLevel(logging.ERROR)
# Also filter torchao print to stderr about cpp extensions
sys.stderr.add_filter("Skipping import of cpp extensions")
# SyntaxWarning: invalid escape sequence '\.'
warnings.filterwarnings(
"ignore", message = "invalid escape sequence", category = SyntaxWarning
)
# PYTORCH_CUDA_ALLOC_CONF is deprecated warning from torch
warnings.filterwarnings("ignore", message = "PYTORCH_CUDA_ALLOC_CONF is deprecated")
# TF32 precision deprecation warning from torch
warnings.filterwarnings(
"ignore", message = "Please use the new API settings to control TF32"
)
# Deprecation warnings from torchao
warnings.filterwarnings("ignore", message = "`int4_weight_only` is deprecated")
warnings.filterwarnings("ignore", message = "`int8_weight_only` is deprecated")
# TorchAO deprecated import paths (https://github.com/pytorch/ao/issues/2752)
warnings.filterwarnings(
"ignore",
message = r"Importing.*from torchao\.dtypes.*is deprecated",
category = DeprecationWarning,
)
warnings.filterwarnings(
"ignore",
message = r"Importing BlockSparseLayout from torchao\.dtypes is deprecated",
category = DeprecationWarning,
)
# SWIG builtin type warnings (from bitsandbytes/triton SWIG bindings)
warnings.filterwarnings(
"ignore",
message = r"builtin type Swig.*has no __module__ attribute",
category = DeprecationWarning,
)
# Triton autotuner deprecation (https://github.com/triton-lang/triton/pull/4496)
warnings.filterwarnings(
"ignore",
message = r"warmup, rep, and use_cuda_graph parameters are deprecated",
category = DeprecationWarning,
)
# Python 3.12+ multiprocessing fork warning in multi-threaded processes
warnings.filterwarnings(
"ignore",
message = r".*multi-threaded.*use of fork\(\) may lead to deadlocks",
category = DeprecationWarning,
)
# Resource warnings from internal socket/file operations
warnings.filterwarnings(
"ignore", message = r"unclosed.*socket", category = ResourceWarning
)
warnings.filterwarnings(
"ignore", message = r"unclosed file.*dev/null", category = ResourceWarning
)
# torch 2.9+ pin_memory/is_pinned device arg deprecation
warnings.filterwarnings(
"ignore",
message = r"The `device` argument is deprecated",
category = DeprecationWarning,
)
warnings.filterwarnings(
"ignore",
message = r".*pin_memory.*device.*deprecated",
category = DeprecationWarning,
)
warnings.filterwarnings(
"ignore",
message = r".*is_pinned.*device.*deprecated",
category = DeprecationWarning,
)
# vllm "Level is deprecated" stderr noise
sys.stderr.add_filter("Level is deprecated")
# PydanticSerializationUnexpectedValue warning
warnings.filterwarnings(
"ignore",
message = r".*PydanticSerializationUnexpectedValue",
)
warnings.filterwarnings(
"ignore",
message = r"Expected.*but got.*with value.*is not.*subclass",
)
# Triton "df: No such file or directory" stderr noise
sys.stderr.add_filter("df: No such file")
# ROCm/libdrm missing ids table stderr noise on some AMD setups
sys.stderr.add_filter(_AMDGPU_IDS_MISSING_TEXT)
# Apex ROCm fused RoPE backend selection warning when Aiter is enabled.
warnings.filterwarnings(
"ignore",
message = r"^Aiter backend is selected for fused RoPE\.?",
category = UserWarning,
module = r"^apex\.transformer\.functional\.fused_rope$",
)
# Fix up AttributeError: 'MessageFactory' object has no attribute 'GetPrototype'
# MUST do this at the start primarily due to tensorflow causing issues
def fix_message_factory_issue():
try:
import google.protobuf.message_factory
class MessageFactory:
def CreatePrototype(self, *args, **kwargs):
return
def GetMessages(self, *args, **kwargs):
return
def GetPrototype(self, *args, **kwargs):
return
if not hasattr(google.protobuf.message_factory, "MessageFactory"):
logger.info("Unsloth: Patching protobuf.MessageFactory as it doesn't exist")
google.protobuf.message_factory.MessageFactory = MessageFactory
elif (
hasattr(google.protobuf.message_factory, "MessageFactory")
and not hasattr(
google.protobuf.message_factory.MessageFactory, "GetPrototype"
)
and not hasattr(google.protobuf.message_factory, "GetMessageClass")
):
google.protobuf.message_factory.MessageFactory = MessageFactory
logger.info("Unsloth: Patching protobuf.MessageFactory as it doesn't exist")
elif (
hasattr(google.protobuf.message_factory, "MessageFactory")
and not hasattr(
google.protobuf.message_factory.MessageFactory, "GetPrototype"
)
and hasattr(google.protobuf.message_factory, "GetMessageClass")
):
GetMessageClass = google.protobuf.message_factory.GetMessageClass
def GetPrototype(self, descriptor):
return GetMessageClass(descriptor)
google.protobuf.message_factory.MessageFactory.GetPrototype = GetPrototype
logger.info("Unsloth: Patching protobuf.MessageFactory.GetPrototype")
pass
except:
pass
# Fix Xformers performance issues since 0.0.25
def fix_xformers_performance_issue():
spec = importlib.util.find_spec("xformers")
if spec is None:
return
xformers_version = importlib_version("xformers")
if Version(xformers_version) < Version("0.0.29"):
xformers_location = spec.origin
if xformers_location is None:
xformers_location = spec.submodule_search_locations[0]
else:
xformers_location = os.path.split(xformers_location)[0]
cutlass = Path(xformers_location) / "ops" / "fmha" / "cutlass.py"
try:
if cutlass.exists():
with open(cutlass, "r+", encoding = "utf-8") as f:
text = f.read()
# See https://github.com/facebookresearch/xformers/issues/1176#issuecomment-2545829591
if "num_splits_key=-1," in text:
text = text.replace(
"num_splits_key=-1,",
"num_splits_key=None,",
)
f.seek(0)
f.write(text)
f.truncate()
logger.info(
"Unsloth: Patching Xformers to fix some performance issues."
)
except Exception as e:
logger.info(f"Unsloth: Failed patching Xformers with error = {str(e)}")
def patch_vllm_for_notebooks():
import sys
ipython = None
try:
from IPython import get_ipython as _get_ipython
except Exception:
_get_ipython = None
if _get_ipython is not None:
try:
ipython = _get_ipython()
except Exception:
ipython = None
if ipython is None:
try:
import builtins
_get_ipython = getattr(builtins, "get_ipython", None)
if callable(_get_ipython):
ipython = _get_ipython()
except Exception:
ipython = None
if ipython is None:
return
try:
shell = ipython.__class__.__name__
is_notebook = shell == "ZMQInteractiveShell" or "google.colab" in str(
type(ipython)
)
except Exception:
return
if not is_notebook:
return
if not hasattr(sys.stdout, "fileno"):
return
needs_patch = False
try:
fd = sys.stdout.fileno()
if not isinstance(fd, int) or fd < 0:
needs_patch = True
except Exception:
needs_patch = True
if not needs_patch:
return
logger.info(
"Unsloth: Notebook detected - Patching sys.stdout.fileno for newer `vllm>=0.12.0` versions"
)
sys.stdout.fileno = lambda: 1
# ValueError: 'aimv2' is already used by a Transformers config, pick another name.
def fix_vllm_aimv2_issue():
spec = importlib.util.find_spec("vllm")
if spec is None:
return
vllm_version = importlib_version("vllm")
if Version(vllm_version) < Version("0.10.1"):
vllm_location = spec.origin
if vllm_location is None:
vllm_location = spec.submodule_search_locations[0]
else:
vllm_location = os.path.split(vllm_location)[0]
ovis_config = Path(vllm_location) / "transformers_utils" / "configs" / "ovis.py"
try:
if ovis_config.exists():
with open(ovis_config, "r+", encoding = "utf-8") as f:
text = f.read()
# See https://github.com/vllm-project/vllm-ascend/issues/2046
if 'AutoConfig.register("aimv2", AIMv2Config)' in text:
text = text.replace(
'AutoConfig.register("aimv2", AIMv2Config)',
"",
)
text = text.replace(
"""backbone_config.pop('model_type')
backbone_config = AutoConfig.for_model(model_type,
**backbone_config)""",
"""if model_type != "aimv2":
backbone_config.pop('model_type')
backbone_config = AutoConfig.for_model(model_type, **backbone_config)
else:
backbone_config = AIMv2Config(**backbone_config)""",
)
f.seek(0)
f.write(text)
f.truncate()
logger.info(
"Unsloth: Patching vLLM to fix `'aimv2' is already used by a Transformers config, pick another name.`"
)
except Exception as e:
logger.info(f"Unsloth: Failed patching vLLM with error = {str(e)}")
def fix_vllm_guided_decoding_params():
def _maybe_raise_vllm_transformers_mismatch(error):
error_text = str(error)
if (
"ALLOWED_LAYER_TYPES" in error_text
or "transformers.configuration_utils" in error_text
):
try:
vllm_version = importlib_version("vllm")
except Exception:
vllm_version = "unknown"
raise RuntimeError(
"Unsloth: vLLM with version "
f"{vllm_version} does not yet support transformers>=5.0.0. "
"Please downgrade to transformers==4.57.3 via "
'pip install --force-reinstall "transformers==4.57.3". '
f"Original error: {error}"
) from error
if importlib.util.find_spec("vllm") is None:
return
# GuidedDecodingParmas is renamed to StructuredOutputsParams in vLLM
# https://github.com/vllm-project/vllm/pull/22772/files
# trl still wants to use GuidedDecodingParams. This is a temporary patch till trl updates
try:
import vllm
except (ImportError, OSError) as e:
_maybe_raise_vllm_transformers_mismatch(e)
if disable_broken_vllm(e):
return
raise
try:
from vllm.sampling_params import GuidedDecodingParams
except (ImportError, OSError) as e:
_maybe_raise_vllm_transformers_mismatch(e)
if disable_broken_vllm(e):
return
if not hasattr(vllm, "sampling_params") or not hasattr(
vllm.sampling_params, "StructuredOutputsParams"
):
raise
vllm.sampling_params.GuidedDecodingParams = (
vllm.sampling_params.StructuredOutputsParams
)
def ignore_logger_messages():
# Ignore Environment variable `HF_TOKEN` is set
try:
from huggingface_hub._login import logger as huggingface_hub_logger
huggingface_hub_logger.addFilter(HideLoggingMessage("`HF_TOKEN`"))
del huggingface_hub_logger
except:
pass
def patch_ipykernel_hf_xet():
# HF-XET == 1.1.10 and ipykernel == 7.0.0 / 7.0.1 causes issues
# See https://github.com/huggingface/xet-core/issues/526
# 2025-10-13T20:37:33.028737Z ERROR Python exception updating progress:, error: PyErr { type: <class 'LookupError'>, value: LookupError(<ContextVar name='shell_parent' at 0x7535b4cebd80>), traceback: Some(<traceback object at 0x753408489f40>) }, caller: "src/progress_update.rs:313"
# at /home/runner/work/xet-core/xet-core/error_printer/src/lib.rs:28
if importlib.util.find_spec("hf_xet") is None:
return
if importlib.util.find_spec("ipykernel") is None:
return
if importlib.util.find_spec("huggingface_hub") is None:
return
ipykernel_version = Version(importlib_version("ipykernel"))
if (
(Version(importlib_version("hf_xet")) == Version("1.1.10"))
and (
(ipykernel_version == Version("7.0.0"))
or (
ipykernel_version == Version("7.0.1")
) # 7.0.1 seems to also break with LookupError: <ContextVar name='shell_parent' at 0x7a9775143ec0>
)
):
print(
"#### Unsloth: `hf_xet==1.1.10` and `ipykernel==7.0.0` or `ipykernel==7.0.1` breaks progress bars. Using ASCII progress bars.\n"
"#### Unsloth: To re-enable progress bars, please upgrade to `ipykernel>=7.1.0` or wait for a fix to\n"
"https://github.com/huggingface/xet-core/issues/526"
)
from huggingface_hub.utils import disable_progress_bars
disable_progress_bars()
def patch_trackio():
# Set some environment variables to customize the Trackio dashboard for experiment tracking
# See https://github.com/unslothai/notebooks/pull/110
os.environ["TRACKIO_LOGO_LIGHT_URL"] = (
"https://raw.githubusercontent.com/unslothai/unsloth/main/images/unsloth%20logo%20black%20text.png"
)
os.environ["TRACKIO_LOGO_DARK_URL"] = (
"https://raw.githubusercontent.com/unslothai/unsloth/main/images/unsloth%20logo%20white%20text.png"
)
os.environ["TRACKIO_PLOT_ORDER"] = "train/reward"
def patch_datasets():
# Datasets 4.4.0 and 4.4.1 weirdly have some weird `_thread.RLock_recursion_count` issues
if importlib.util.find_spec("datasets") is None:
return
datasets_version = Version(importlib_version("datasets"))
if (datasets_version <= Version("4.5.0")) and (
datasets_version >= Version("4.4.0")
):
raise NotImplementedError(
f"#### Unsloth: Using `datasets = {str(datasets_version)}` will cause recursion errors.\n"
"Please downgrade datasets to `datasets==4.3.0"
)
def check_fbgemm_gpu_version():
if importlib.util.find_spec("fbgemm_gpu") is None:
return
try:
fbgemm_gpu_version = importlib_version("fbgemm_gpu_genai")
except:
return
# We noticed some SegFault or bad alloc errors on lower versions of fbgemm_gpu.
# Instead of raising an error, disable FBGEMM and fall back to Triton kernels.
if Version(fbgemm_gpu_version) < Version("1.4.0"):
os.environ["UNSLOTH_HAS_FBGEMM"] = "0"
logger.info(
f"Unsloth: fbgemm_gpu_genai=={fbgemm_gpu_version} is old and may cause issues. "
f"Disabling FBGEMM - using Triton kernels instead."
)
return
logger.info(f"Unsloth: fbgemm_gpu_genai=={fbgemm_gpu_version} detected.")
def patch_enable_input_require_grads():
"""
Patch transformers PreTrainedModel.enable_input_require_grads to handle vision models
that raise NotImplementedError from get_input_embeddings().
"""
import inspect
from transformers import PreTrainedModel
# Check if the original function iterates over self.modules() instead of just returning the enable_input_require_grads
# Ref: https://github.com/huggingface/transformers/pull/41993/files#diff-6b72b98c4c2dcfc6cc606843917733f5d858374fbc22a735ff483bbc0c1e63eaL1979-R1996
try:
original_source = inspect.getsource(PreTrainedModel.enable_input_require_grads)
except:
return
# Only patch if the new pattern exists (iterating over self.modules())
if "for module in self.modules()" not in original_source:
return
def _patched_enable_input_require_grads(self):
def make_inputs_require_grads(module, input, output):
output.requires_grad_(True)
hooks = []
seen_modules = set()
for module in self.modules():
if not (
isinstance(module, PreTrainedModel)
and hasattr(module, "get_input_embeddings")
):
continue
try:
input_embeddings = module.get_input_embeddings()
except NotImplementedError:
# Vision models may not implement get_input_embeddings - skip them
# For GLM V4.6 for example, this skips only `self.visual`
continue
if input_embeddings is None:
continue
embedding_id = id(input_embeddings)
if embedding_id in seen_modules:
continue
seen_modules.add(embedding_id)
hooks.append(
input_embeddings.register_forward_hook(make_inputs_require_grads)
)
self._require_grads_hooks = hooks
if hooks:
self._require_grads_hook = hooks[0]
PreTrainedModel.enable_input_require_grads = _patched_enable_input_require_grads
logger.info(
"Unsloth: Patched enable_input_require_grads for vision model compatibility"
)
def _is_custom_torch_build(raw_version_str):
"""Check if a raw version string indicates a custom or source build.
Must operate on the raw string from importlib_version(), not the parsed
Version object, since our custom Version() strips local identifiers.
Standard PyTorch releases use: +cu124, +rocm6.3, +cpu, +xpu
Source/custom builds use: +gitXXXXXXX, +HEXHASH, or other suffixes.
"""
if "+" not in raw_version_str:
return False
local = raw_version_str.split("+", 1)[1]
if not local:
return False
# Use fullmatch so the entire local identifier must match, not just a prefix.
# cu/rocm require a trailing digit (e.g. cu124, rocm6.3). cpu/xpu are exact.
# Case-insensitive since some builds may use uppercase.
return not re.fullmatch(r"cu\d[\d.]*|rocm\d[\d.]*|cpu|xpu", local, re.IGNORECASE)
def _infer_required_torchvision(torch_major, torch_minor):
"""Infer the minimum required torchvision minor version from torch version.
The torch -> torchvision minor version mapping follows a consistent formula:
torch 1.x -> torchvision 0.(x + 1) (verified: torch 1.7 through 1.13)
torch 2.x -> torchvision 0.(x + 15) (verified: torch 2.0 through 2.9)
Returns (tv_major, tv_minor) or None if the major version is unrecognized.
"""
if torch_major == 1 and torch_minor >= 7:
return (0, torch_minor + 1)
if torch_major == 2:
return (0, torch_minor + 15)
return None
def torchvision_compatibility_check():
# Allow skipping via environment variable for custom environments
if os.environ.get("UNSLOTH_SKIP_TORCHVISION_CHECK", "0").lower() in ("1", "true"):
return
if importlib.util.find_spec("torch") is None:
raise ImportError("Unsloth: torch not found. Please install torch first.")
if importlib.util.find_spec("torchvision") is None:
return
try:
torch_version_raw = importlib_version("torch")
torchvision_version_raw = importlib_version("torchvision")
except Exception:
return
try:
torch_v = Version(torch_version_raw)
tv_v = Version(torchvision_version_raw)
except Exception:
return
# Known compatibility table (ground truth, takes precedence over formula).
# See https://pytorch.org/get-started/previous-versions/
TORCH_TORCHVISION_COMPAT = {
(2, 9): (0, 24),
(2, 8): (0, 23),
(2, 7): (0, 22),
(2, 6): (0, 21),
(2, 5): (0, 20),
(2, 4): (0, 19),
}
# Extract major.minor from the parsed version
torch_release = torch_v.release
if len(torch_release) < 2:
return
torch_major, torch_minor = torch_release[0], torch_release[1]
# Try known table first, then fall back to formula for forward compatibility
required = TORCH_TORCHVISION_COMPAT.get((torch_major, torch_minor))
if required is None:
required = _infer_required_torchvision(torch_major, torch_minor)
if required is None:
return
required_tv_str = f"{required[0]}.{required[1]}.0"
if tv_v >= Version(required_tv_str):
logger.info(
f"Unsloth: torch=={torch_version_raw} and "
f"torchvision=={torchvision_version_raw} are compatible."
)
return
# Version mismatch detected
message = (
f"Unsloth: torch=={torch_version_raw} requires "
f"torchvision>={required_tv_str}, "
f"but found torchvision=={torchvision_version_raw}. "
f'Try updating torchvision via `pip install --upgrade "torchvision>={required_tv_str}"`. '
f"Please refer to https://pytorch.org/get-started/previous-versions/ "
f"for more information."
)
is_custom = _is_custom_torch_build(torch_version_raw) or _is_custom_torch_build(
torchvision_version_raw
)
# Detect nightly/dev/alpha/beta/rc builds from the raw version string.
# These often have version mismatches that are expected.
_pre_tags = (".dev", "a0", "b0", "rc", "alpha", "beta", "nightly")
is_prerelease = any(t in torch_version_raw for t in _pre_tags) or any(
t in torchvision_version_raw for t in _pre_tags
)
# Only downgrade to warning for custom/source or prerelease builds.
# Stable mismatches should fail fast to prevent runtime operator errors.
if is_custom or is_prerelease:
reason = "custom/source build" if is_custom else "pre-release build"
logger.warning(
f"{message}\n"
f"Detected a {reason}. "
f"Continuing with a warning. "
f"Set UNSLOTH_SKIP_TORCHVISION_CHECK=1 to silence this."
)
return
raise ImportError(message)
# Fix TRL OpenEnv 0.26 NameError: name 'SamplingParams' is not defined
def fix_openenv_no_vllm():
spec = importlib.util.find_spec("trl")
if spec is None:
return
trl_location = spec.origin
if trl_location is None:
trl_location = spec.submodule_search_locations[0]
else:
trl_location = os.path.split(trl_location)[0]
openenv = Path(trl_location) / "experimental" / "openenv" / "utils.py"
if not openenv.exists():
return
try:
with open(openenv, "r+", encoding = "utf-8") as f:
text = f.read()
bad = (
"if is_vllm_available():\n"
" from vllm import SamplingParams\n"
" from vllm.sampling_params import GuidedDecodingParams\n"
)
replace_with = bad + (
"else:\n"
" from typing import Any\n"
" SamplingParams = Any\n"
" GuidedDecodingParams = Any\n"
"\n"
)
if bad + "\n" + "\n" in text and replace_with not in text:
text = text.replace(bad + "\n" + "\n", replace_with)
f.seek(0)
f.write(text)
f.truncate()
logger.info(
"Unsloth: Patching TRL OpenEnv to fix SamplingParams not defined"
)
except Exception as e:
logger.info(f"Unsloth: Failed patching TRL OpenEnv with error = {str(e)}")
# Fix Exeuctorch needing get_mapped_key
def fix_executorch():
spec = importlib.util.find_spec("executorch")
if spec is None:
return
executorch_location = spec.origin
if executorch_location is None:
executorch_location = spec.submodule_search_locations[0]
else:
executorch_location = os.path.split(executorch_location)[0]
executorch = Path(executorch_location) / "examples" / "models" / "__init__.py"
if not executorch.exists():
return
try:
what = r"""
import sys
import types
import re
from typing import Any, Optional
def get_mapped_key(key: str, mapping_dict: dict[str, str]) -> str:
try:
# Checks if there is a layer # in the key
if any(k.isdigit() for k in key.split(".")):
# Replace layer number with "{}" to create key for lookup
abstract_key = re.sub(r"(\.\d+)", ".{}", key)
layer_num = re.search(r"\d+", key).group(0)
new_key = mapping_dict[abstract_key]
new_key = new_key.format(layer_num)
else:
new_key = mapping_dict[key]
except KeyError as e:
raise Exception(
f'Error converting the state dict. Found unexpected key: "{key}". '
"Please make sure you're loading a checkpoint with the right format. "
) from e
return new_key
torchtune = types.ModuleType("torchtune")
torchtune.__path__ = []
models = types.ModuleType("torchtune.models")
models.__path__ = []
convert_weights = types.ModuleType("torchtune.models.convert_weights")
convert_weights.get_mapped_key = get_mapped_key
torchtune.models = models
models.convert_weights = convert_weights
sys.modules["torchtune"] = torchtune
sys.modules["torchtune.models"] = models
sys.modules["torchtune.models.convert_weights"] = convert_weights
"""
what = textwrap.dedent(what)
with open(executorch, "r+", encoding = "utf-8") as f:
text = f.read()
bad = "from enum import Enum\n"
if bad in text and what not in text:
text = text.replace(bad + "\n", bad + "\n" + what)
f.seek(0)
f.write(text)
f.truncate()
logger.info("Unsloth: Patching Executorch to fix get_mapped_key")
except Exception as e:
logger.info(f"Unsloth: Failed Executorch with error = {str(e)}")
def fix_diffusers_warnings():
# Silence Flax classes are deprecated and will be removed in Diffusers v1.0.0.
os.environ["DIFFUSERS_VERBOSITY"] = "error"
def fix_huggingface_hub():
# huggingface_hub.is_offline_mode got removed, so add it back
import huggingface_hub
if not hasattr(huggingface_hub, "is_offline_mode"):
huggingface_hub.is_offline_mode = (
lambda: huggingface_hub.constants.HF_HUB_OFFLINE
)
def fix_triton_compiled_kernel_missing_attrs():
"""
Triton 3.6.0+ removed direct `num_ctas` and `cluster_dims` attributes from
CompiledKernel, but torch 2.9.x Inductor still expects them in
torch/_inductor/runtime/triton_heuristics.py make_launcher() (line ~1757).
The scope dict eagerly evaluates:
binary.metadata.num_ctas, *binary.metadata.cluster_dims
when hasattr(binary, "metadata") is True, but metadata lacks cluster_dims.
This crashes before reaching the new launch path that doesn't need cta_args.
Upstream fix: pytorch/pytorch@97bd4db added hasattr guards.
We monkey-patch CompiledKernel.__init__ to inject the missing attributes
so the older hasattr(binary, "num_ctas") branch succeeds instead.
"""
try:
import torch
except (ImportError, ModuleNotFoundError):
return
try:
import triton
import triton.compiler.compiler as triton_compiler
except (ImportError, ModuleNotFoundError):
return
# Only needed when the CompiledKernel class lacks num_ctas as a direct attr
# but has metadata (triton >= 3.6.0 with torch < 2.10)
_ck_cls = triton_compiler.CompiledKernel
if hasattr(_ck_cls, "num_ctas"):
return # Old triton with direct attrs -- no patch needed
_orig_init = _ck_cls.__init__
def _patched_init(self, *args, **kwargs):
_orig_init(self, *args, **kwargs)
if not hasattr(self, "num_ctas"):
self.num_ctas = getattr(self.metadata, "num_ctas", 1)
if not hasattr(self, "cluster_dims") and not hasattr(self, "clusterDims"):
self.cluster_dims = (1, 1, 1)
_ck_cls.__init__ = _patched_init
logger.info(
"Unsloth: Patched triton CompiledKernel with num_ctas/cluster_dims "
"for torch.compile compatibility."
)
def patch_trunc_normal_precision_issue():
"""
Patch torch.nn.init.trunc_normal_ for low precision tensors to run init in fp32.
torch.nn.init.trunc_normal_ can saturate at truncation bounds in fp16/bf16 on
some versions/backends. This was observed in TorchTitan investigations where
low-precision truncation produced boundary-heavy initialization behavior:
https://github.com/pytorch/torchtitan/pull/2342
To avoid that failure mode, initialize into a temporary fp32 tensor, then copy
back to the original dtype.
"""
try:
import torch
except (ImportError, ModuleNotFoundError):
return
if getattr(torch.nn.init, "_unsloth_trunc_normal_patched", False):
return
original_trunc_normal = torch.nn.init.trunc_normal_
if getattr(original_trunc_normal, "__unsloth_trunc_normal_patched__", False):
torch.nn.init._unsloth_trunc_normal_patched = True
return
low_precision_dtypes = {torch.float16, torch.bfloat16}
def _call_original(target, mean, std, a, b, generator):
if generator is None:
return original_trunc_normal(target, mean = mean, std = std, a = a, b = b)
try:
return original_trunc_normal(
target, mean = mean, std = std, a = a, b = b, generator = generator
)
except TypeError as exc:
# Older torch versions may not accept a generator keyword argument.
msg = str(exc).lower()
if "unexpected keyword argument" in msg and "generator" in msg:
return original_trunc_normal(target, mean = mean, std = std, a = a, b = b)
raise
try:
from torch.distributed._tensor import DTensor
except Exception:
DTensor = None
@torch.no_grad()
def _patched_trunc_normal_(
tensor,
mean: float = 0.0,
std: float = 1.0,
a: float = -2.0,
b: float = 2.0,
generator = None,
):
if DTensor is not None and isinstance(tensor, DTensor):
local_tensor = getattr(tensor, "_local_tensor", None)
if local_tensor is None:
return _call_original(tensor, mean, std, a, b, generator)
if local_tensor.dtype in low_precision_dtypes:
local_fp32 = local_tensor.float()
_call_original(local_fp32, mean, std, a, b, generator)
local_tensor.copy_(local_fp32.to(dtype = local_tensor.dtype))
return tensor
return _call_original(tensor, mean, std, a, b, generator)
if tensor.dtype in low_precision_dtypes:
tensor_fp32 = tensor.float()
_call_original(tensor_fp32, mean, std, a, b, generator)
tensor.copy_(tensor_fp32.to(dtype = tensor.dtype))
return tensor
return _call_original(tensor, mean, std, a, b, generator)
_patched_trunc_normal_.__unsloth_trunc_normal_patched__ = True
_patched_trunc_normal_._unsloth_original = original_trunc_normal
torch.nn.init._unsloth_trunc_normal_original = original_trunc_normal
torch.nn.init.trunc_normal_ = _patched_trunc_normal_
torch.nn.init._unsloth_trunc_normal_patched = True
logger.info("Unsloth: Patched torch.nn.init.trunc_normal_ for fp16/bf16 stability.")
def check_vllm_torch_sm100_compatibility():
"""
Check for incompatible vLLM + torch < 2.9.0 + SM100 (Blackwell) combination.
vLLM's distributed module (device_communicators) crashes with std::bad_alloc
when imported on SM100 GPUs (B200/B100) with torch < 2.9.0. This is due to
C++ code in vLLM's NCCL/distributed layer being incompatible with older
torch versions on the newer Blackwell architecture.
This check runs early (before vLLM import) to provide a helpful error message
instead of a cryptic std::bad_alloc crash.
"""
# Check if vLLM is installed (without importing it)
if importlib.util.find_spec("vllm") is None:
return
# Check torch version
try:
torch_version = Version(importlib_version("torch"))
if torch_version >= Version("2.9.0"):
return # torch >= 2.9.0 is compatible
except Exception:
return # Can't determine torch version, skip check
# Check if any CUDA GPU is SM100 (Blackwell)
try:
import torch
if not torch.cuda.is_available():
return
has_sm100 = False
sm100_gpu_name = None
for i in range(torch.cuda.device_count()):
major, minor = torch.cuda.get_device_capability(i)
if major == 10:
has_sm100 = True
sm100_gpu_name = torch.cuda.get_device_name(i)
break
if not has_sm100:
return
except Exception:
return
# Get vLLM version for the error message
try:
vllm_version = importlib_version("vllm")
except Exception:
vllm_version = "unknown"
# Incompatible combination detected - raise helpful error
raise RuntimeError(
f"Unsloth: Incompatible configuration detected.\n\n"
f" GPU: {sm100_gpu_name} (SM100 / Blackwell architecture)\n"
f" torch version: {torch_version}\n"
f" vLLM version: {vllm_version}\n\n"
f"vLLM's distributed module crashes with std::bad_alloc on SM100 GPUs "
f"(B200/B100/Blackwell) when using torch < 2.9.0.\n\n"
f"To fix this, please upgrade torch:\n"
f" pip install --upgrade torch>=2.9.0\n\n"
f"Alternatively, if you don't need vLLM:\n"
f" pip uninstall vllm"
)
def fix_vllm_pdl_blackwell():
"""
Fix vLLM PDL (Programmatic Dependent Launch) bug on Blackwell GPUs (SM100).
The issue: vLLM's LoRA Triton kernels use tl.extra.cuda.gdc_wait() for PDL
optimization on SM90+ GPUs. This fails on SM100 (B200/B100) during CUDA graph
capture because Triton's pipeliner can't handle gdc_wait in complex kernels.
See: https://github.com/vllm-project/vllm/issues/30872
"""
if importlib.util.find_spec("vllm") is None:
return
# Check if any CUDA GPU is SM100 (Blackwell)
try:
import torch
if not torch.cuda.is_available():
return
# Scan all GPUs for SM100 - fix applies globally via env var and monkey-patch
has_sm100 = False
sm100_gpu_name = None
for i in range(torch.cuda.device_count()):
major, minor = torch.cuda.get_device_capability(i)
if major == 10:
has_sm100 = True
sm100_gpu_name = torch.cuda.get_device_name(i)
break
if not has_sm100:
return
except Exception:
return
# Helper to check if module spec exists
def _spec_exists(name):
try:
return importlib.util.find_spec(name) is not None
except (ImportError, OSError, ModuleNotFoundError, ValueError):
return False
# Check if vLLM has the PDL-related modules before doing internet check
has_utils = _spec_exists("vllm.lora.ops.triton_ops.utils")
has_expand_op = _spec_exists("vllm.lora.ops.triton_ops.lora_expand_op")
has_shrink_op = _spec_exists("vllm.lora.ops.triton_ops.lora_shrink_op")
if not has_utils and not has_expand_op and not has_shrink_op:
# Old vLLM version without PDL support - nothing to patch
return
# Check if vLLM version includes the fix
VLLM_PDL_FIX_VERSION = "0.15.0"
try:
vllm_version = Version(importlib_version("vllm"))
if vllm_version >= Version(VLLM_PDL_FIX_VERSION):
logger.info(
f"Unsloth: SM100 ({sm100_gpu_name}) detected but vLLM {vllm_version} "
f"should include PDL fix - skipping workaround"
)
return
except Exception as e:
logger.debug(
f"Unsloth: vLLM version check failed ({e}), applying PDL workaround."
)
# Apply the PDL fix
os.environ["TRITON_DISABLE_PDL"] = "1"
def fake_supports_pdl(*args, **kwargs):
return False
patched = []
patched_names = set()
def _record_patch(name):
if name not in patched_names:
patched.append(name)
patched_names.add(name)
# First, patch the source module (utils.py) where supports_pdl is defined.
# This is critical because supports_pdl uses @lru_cache - we must clear the
# cache to prevent stale cached results from the original function.
try:
utils_module = importlib.import_module("vllm.lora.ops.triton_ops.utils")
if hasattr(utils_module, "supports_pdl"):
original_fn = utils_module.supports_pdl
if hasattr(original_fn, "cache_clear"):
original_fn.cache_clear()
utils_module.supports_pdl = fake_supports_pdl
_record_patch("utils")
except (ImportError, ModuleNotFoundError, AttributeError):
pass
# Also patch the consumer modules that import supports_pdl from utils.
# This ensures the patched function is used even if the module was already
# imported before this fix runs.
consumer_modules = {
"lora_expand_op": "vllm.lora.ops.triton_ops.lora_expand_op",
"lora_shrink_op": "vllm.lora.ops.triton_ops.lora_shrink_op",
"fused_moe_lora_op": "vllm.lora.ops.triton_ops.fused_moe_lora_op",
}
for name, path in consumer_modules.items():
try:
module = importlib.import_module(path)
if hasattr(module, "supports_pdl"):
module.supports_pdl = fake_supports_pdl
_record_patch(name)
except (ImportError, ModuleNotFoundError, AttributeError):
pass
# Patch any additional already-loaded triton ops consumers that expose supports_pdl.
for module_name, module in tuple(sys.modules.items()):
if not module_name.startswith("vllm.lora.ops.triton_ops."):
continue
if module is None or not hasattr(module, "supports_pdl"):
continue
module.supports_pdl = fake_supports_pdl
_record_patch(module_name.rsplit(".", 1)[-1])
if patched:
logger.info(
f"Unsloth: Applied PDL fix for SM100 ({sm100_gpu_name}) - "
f"patched: {', '.join(patched)}"
)
else:
# Just set the env var - vLLM might be an older version without supports_pdl
logger.info(f"Unsloth: Set TRITON_DISABLE_PDL=1 for SM100 ({sm100_gpu_name})")
def patch_openspiel_env_async():
"""Apply nest_asyncio for OpenEnv EnvClient async compatibility.
OpenEnv's EnvClient uses async methods (reset/step). In Jupyter notebooks
these work via top-level await, but converted scripts need
asyncio.get_event_loop().run_until_complete() wrappers. Applying nest_asyncio
ensures nested event loop calls work in all contexts without replacing the
original async methods (which would break scripts that already have their own
sync wrappers).
"""
try:
import inspect
from openenv.core.env_client import EnvClient
if not inspect.iscoroutinefunction(EnvClient.reset):
return # Already sync, nothing to do
try:
import nest_asyncio
nest_asyncio.apply()
logger.info(
"Unsloth: Applied nest_asyncio for OpenEnv EnvClient async compatibility"
)
except ImportError:
logger.info(
"Unsloth: nest_asyncio not installed, OpenEnv async methods may need manual wrapping"
)
except (ImportError, AttributeError):
pass # openenv not installed
def patch_torchcodec_audio_decoder():
"""Call unsloth_zoo's AudioDecoder patch."""
try:
from unsloth_zoo.dataset_utils import patch_torchcodec_audio_decoder as _patch
_patch()
except (ImportError, AttributeError, RuntimeError):
pass
def disable_torchcodec_if_broken():
"""Disable torchcodec in transformers if it cannot actually load.
transformers checks if torchcodec is installed via importlib.util.find_spec(),
but this returns True even when torchcodec cannot load its native libraries
(e.g., when FFmpeg is missing). This causes runtime errors when transformers
tries to use torchcodec for audio loading.
This function tests if torchcodec can actually load and if not, patches
transformers to think torchcodec is unavailable so it falls back to librosa.
"""
try:
import importlib.util
if importlib.util.find_spec("torchcodec") is None:
return # torchcodec not installed, nothing to do
# Test if torchcodec can actually load
from torchcodec.decoders import AudioDecoder
except (ImportError, RuntimeError, OSError):
# torchcodec cannot load - disable it in transformers
try:
import transformers.utils.import_utils as tf_import_utils
tf_import_utils._torchcodec_available = False
except (ImportError, AttributeError):
pass
def disable_broken_wandb():
"""Disable wandb if it's installed but cannot actually import.
wandb can fail to import when there's a protobuf version mismatch
(e.g., wandb < 0.19.11 with protobuf >= 6.0). This causes cascading
import failures through trl -> transformers/accelerate -> wandb that
crash unsloth's import chain.
There are two separate is_wandb_available() functions used by trl:
- transformers.integrations.integration_utils.is_wandb_available
(used by most trl trainers)
- accelerate.utils.imports.is_wandb_available
(used by trl/trainer/callbacks.py)
Both must be patched to fully prevent broken wandb imports.
"""
if importlib.util.find_spec("wandb") is None:
return # wandb not installed, nothing to do
try:
import wandb
except Exception:
# wandb is installed but broken - patch all checkers to skip it
logger.info(
"Unsloth: wandb is installed but broken (likely a protobuf version mismatch). "
"Disabling wandb to prevent import errors. To fix, run: pip install --upgrade wandb"
)
_wandb_false = lambda: False
# Patch transformers' is_wandb_available (used by most trl trainers)
try:
import transformers.integrations.integration_utils as tf_integration
tf_integration.is_wandb_available = _wandb_false
except (ImportError, AttributeError):
pass
# Patch accelerate's is_wandb_available (used by trl/trainer/callbacks.py).
# Must patch both the source module AND the re-export namespace since
# `from accelerate.utils import is_wandb_available` reads from
# accelerate.utils, not accelerate.utils.imports.
try:
import accelerate.utils.imports as acc_imports
acc_imports.is_wandb_available = _wandb_false
except (ImportError, AttributeError):
pass
try:
import accelerate.utils as acc_utils
acc_utils.is_wandb_available = _wandb_false
except (ImportError, AttributeError):
pass
# Set env var as additional fallback
os.environ["WANDB_DISABLED"] = "true"
CAUSAL_CONV1D_BROKEN = False
_CAUSAL_CONV1D_PREFIX = "causal_conv1d"
_CAUSAL_CONV1D_BLOCKER_SENTINEL = "_unsloth_causal_conv1d_blocker"
VLLM_BROKEN = False
_VLLM_PREFIX = "vllm"
_VLLM_BLOCKER_SENTINEL = "_unsloth_vllm_blocker"
_ROCM_ENV_HINT_KEYS = (
"ROCM_PATH",
"ROCM_HOME",
"HIP_PATH",
"HSA_PATH",
"HIP_VISIBLE_DEVICES",
"ROCR_VISIBLE_DEVICES",
)
_ROCM_PATH_HINTS = (
Path("/opt/rocm"),
Path("/dev/kfd"),
Path("/sys/module/amdgpu"),
)
_AMDGPU_ASIC_ID_TABLE_PATH_ENV = "AMDGPU_ASIC_ID_TABLE_PATH"
_AMDGPU_ASIC_ID_CANDIDATE_PATHS = (
Path("/usr/share/libdrm/amdgpu.ids"),
Path("/usr/local/share/libdrm/amdgpu.ids"),
Path("/opt/rocm/share/libdrm/amdgpu.ids"),
Path("/opt/amdgpu/share/libdrm/amdgpu.ids"),
)
def _log_rocm_detection(message):
if UNSLOTH_ENABLE_LOGGING:
logger.info(message)
@functools.lru_cache(1)
def _is_rocm_torch_build() -> bool:
# Most official ROCm wheels include a local version suffix like +rocmX.Y.
# Some custom/source builds do not, so we fall back to runtime hints.
try:
torch_version_raw = str(importlib_version("torch")).lower()
if "rocm" in torch_version_raw:
_log_rocm_detection(
"Unsloth: ROCm detection matched torch version tag (+rocm)."
)
return True
except Exception:
pass
# Environment hints commonly present on ROCm runtimes.
for key in _ROCM_ENV_HINT_KEYS:
value = os.environ.get(key, "")
if isinstance(value, str) and value.strip():
_log_rocm_detection(
f"Unsloth: ROCm detection matched environment key `{key}`."
)
return True
# Filesystem / driver hints for ROCm stacks.
for path in _ROCM_PATH_HINTS:
try:
if path.exists():
_log_rocm_detection(
f"Unsloth: ROCm detection matched filesystem hint `{path}`."
)
return True
except Exception:
continue
_log_rocm_detection("Unsloth: ROCm detection did not match any known hints.")
return False
def _iter_amdgpu_asic_id_table_candidates():
# Try torch-adjacent ids table paths first without importing torch.
try:
torch_spec = importlib.util.find_spec("torch")
except Exception:
torch_spec = None
roots = []
if torch_spec is not None:
if torch_spec.origin:
roots.append(Path(torch_spec.origin).resolve().parent)
if torch_spec.submodule_search_locations:
for location in torch_spec.submodule_search_locations:
roots.append(Path(location).resolve())
seen = set()
for root in roots:
for candidate in (
root / "share" / "libdrm" / "amdgpu.ids",
root.parent / "share" / "libdrm" / "amdgpu.ids",
root.parent.parent / "share" / "libdrm" / "amdgpu.ids",
):
candidate_str = str(candidate)
if candidate_str in seen:
continue
seen.add(candidate_str)
yield candidate
for candidate in _AMDGPU_ASIC_ID_CANDIDATE_PATHS:
candidate_str = str(candidate)
if candidate_str in seen:
continue
seen.add(candidate_str)
yield candidate
def configure_amdgpu_asic_id_table_path():
# Honor an existing valid user-provided path.
configured = os.environ.get(_AMDGPU_ASIC_ID_TABLE_PATH_ENV, "").strip()
if configured:
configured_path = Path(configured)
try:
if configured_path.is_file():
return str(configured_path)
except Exception:
pass
# Only attempt this on ROCm-like environments.
if not _is_rocm_torch_build():
return None
for candidate in _iter_amdgpu_asic_id_table_candidates():
try:
if candidate.is_file():
os.environ[_AMDGPU_ASIC_ID_TABLE_PATH_ENV] = str(candidate)
if UNSLOTH_ENABLE_LOGGING:
logger.info(
f"Unsloth: Set {_AMDGPU_ASIC_ID_TABLE_PATH_ENV}={candidate}"
)
return str(candidate)
except Exception:
continue
return None
def _is_causal_conv1d_name(module_name: str) -> bool:
return module_name == _CAUSAL_CONV1D_PREFIX or module_name.startswith(
_CAUSAL_CONV1D_PREFIX + "."
)
def _is_vllm_name(module_name: str) -> bool:
return module_name == _VLLM_PREFIX or module_name.startswith(_VLLM_PREFIX + ".")
def _resolve_module_name(module_name, package):
if not isinstance(module_name, str):
return module_name
if module_name.startswith("."):
try:
return importlib.util.resolve_name(module_name, package)
except Exception:
return module_name
return module_name
def _is_broken_causal_conv1d_error(error) -> bool:
checked = set()
current = error
while current is not None and id(current) not in checked:
checked.add(id(current))
message = str(current).lower()
if (
("causal_conv1d_cuda" in message and "undefined symbol" in message)
or ("_zn3c103hip28c10_hip_check_implementation" in message)
or ("causal_conv1d" in message and "undefined symbol" in message)
):
return True
current = getattr(current, "__cause__", None) or getattr(
current, "__context__", None
)
return False
def _is_broken_vllm_error(error) -> bool:
checked = set()
current = error
while current is not None and id(current) not in checked:
checked.add(id(current))
message = str(current).lower()
if (
("vllm/_c" in message or "vllm._c" in message)
and (
"undefined symbol" in message
or "cannot open shared object file" in message
or ".so:" in message
)
) or ("vllm" in message and "undefined symbol" in message):
return True
# Also catch CUDA shared library mismatches during vllm import
# e.g. "libcudart.so.12: cannot open shared object file"
if (
"libcudart" in message or "libcublas" in message or "libnvrtc" in message
) and "cannot open shared object file" in message:
return True
current = getattr(current, "__cause__", None) or getattr(
current, "__context__", None
)
return False
def _get_vllm_cuda_mismatch_message(error):
"""If the error is a CUDA version mismatch, return a helpful install message."""
import re as _re
checked = set()
current = error
wanted_cuda = None
while current is not None and id(current) not in checked:
checked.add(id(current))
message = str(current)
# Extract the CUDA version vllm was built for, e.g. "libcudart.so.12"
match = _re.search(r"libcudart\.so\.(\d+)", message)
if match:
wanted_cuda = match.group(1)
break
current = getattr(current, "__cause__", None) or getattr(
current, "__context__", None
)
if wanted_cuda is None:
return None
# Detect what CUDA version is actually available on the system
system_cuda_display = None # Human-readable, e.g. "13.0"
system_cuda_tag = None # For wheel URL, e.g. "130"
try:
import torch
cuda_version = torch.version.cuda # e.g. "13.0" or "12.8"
if cuda_version:
system_cuda_display = cuda_version
system_cuda_tag = cuda_version.replace(".", "")[:3] # "130" or "128"
except Exception:
pass
if system_cuda_tag is None or system_cuda_tag.startswith(wanted_cuda):
return None # Not a mismatch or can't determine
try:
vllm_version = importlib_version("vllm").split("+")[0]
except Exception:
vllm_version = "VLLM_VERSION"
cpu_arch = "x86_64"
try:
import platform
cpu_arch = platform.machine()
except Exception:
pass
return (
f"Unsloth: vLLM was built for CUDA {wanted_cuda} but this system has "
f"CUDA {system_cuda_display}. Please reinstall vLLM with the correct CUDA version:\n"
f"\n"
f" uv pip install https://github.com/vllm-project/vllm/releases/download/"
f"v{vllm_version}/vllm-{vllm_version}+cu{system_cuda_tag}-cp38-abi3-"
f"manylinux_2_35_{cpu_arch}.whl"
)
class _CausalConv1dImportBlockerLoader(importlib.abc.Loader):
__slots__ = ("module_name",)
def __init__(self, module_name):
self.module_name = module_name
def create_module(self, spec):
return None
def exec_module(self, module):
raise ModuleNotFoundError(f"No module named '{self.module_name}'")
class _CausalConv1dImportBlockerFinder(importlib.abc.MetaPathFinder):
__slots__ = (_CAUSAL_CONV1D_BLOCKER_SENTINEL,)
def __init__(self):
setattr(self, _CAUSAL_CONV1D_BLOCKER_SENTINEL, True)
def find_spec(self, fullname, path = None, target = None):
if not CAUSAL_CONV1D_BROKEN or not _is_causal_conv1d_name(fullname):
return None
return importlib.machinery.ModuleSpec(
name = fullname,
loader = _CausalConv1dImportBlockerLoader(fullname),
is_package = fullname == _CAUSAL_CONV1D_PREFIX,
)
class _VllmImportBlockerLoader(importlib.abc.Loader):
__slots__ = ("module_name",)
def __init__(self, module_name):
self.module_name = module_name
def create_module(self, spec):
return None
def exec_module(self, module):
raise ModuleNotFoundError(f"No module named '{self.module_name}'")
class _VllmImportBlockerFinder(importlib.abc.MetaPathFinder):
__slots__ = (_VLLM_BLOCKER_SENTINEL,)
def __init__(self):
setattr(self, _VLLM_BLOCKER_SENTINEL, True)
def find_spec(self, fullname, path = None, target = None):
if not VLLM_BROKEN or not _is_vllm_name(fullname):
return None
return importlib.machinery.ModuleSpec(
name = fullname,
loader = _VllmImportBlockerLoader(fullname),
is_package = fullname == _VLLM_PREFIX,
)
def _patch_find_spec_for_causal_conv1d():
current_find_spec = importlib.util.find_spec
if getattr(current_find_spec, "_unsloth_causal_conv1d_find_spec_patch", False):
return
def _blocked_find_spec(name, package = None):
resolved_name = _resolve_module_name(name, package)
if CAUSAL_CONV1D_BROKEN and isinstance(resolved_name, str):
if _is_causal_conv1d_name(resolved_name):
return None
return current_find_spec(name, package)
_blocked_find_spec._unsloth_causal_conv1d_find_spec_patch = True
_blocked_find_spec._unsloth_original_find_spec = current_find_spec
importlib.util.find_spec = _blocked_find_spec
def _patch_find_spec_for_vllm():
current_find_spec = importlib.util.find_spec
if getattr(current_find_spec, "_unsloth_vllm_find_spec_patch", False):
return
def _blocked_find_spec(name, package = None):
resolved_name = _resolve_module_name(name, package)
if VLLM_BROKEN and isinstance(resolved_name, str):
if _is_vllm_name(resolved_name):
return None
return current_find_spec(name, package)
_blocked_find_spec._unsloth_vllm_find_spec_patch = True
_blocked_find_spec._unsloth_original_find_spec = current_find_spec
importlib.util.find_spec = _blocked_find_spec
def _install_causal_conv1d_blocker():
_patch_find_spec_for_causal_conv1d()
for finder in sys.meta_path:
if getattr(finder, _CAUSAL_CONV1D_BLOCKER_SENTINEL, False):
return
sys.meta_path.insert(0, _CausalConv1dImportBlockerFinder())
def _install_vllm_blocker():
_patch_find_spec_for_vllm()
for finder in sys.meta_path:
if getattr(finder, _VLLM_BLOCKER_SENTINEL, False):
return
sys.meta_path.insert(0, _VllmImportBlockerFinder())
def _clear_causal_conv1d_modules():
for module_name in list(sys.modules):
if _is_causal_conv1d_name(module_name):
sys.modules.pop(module_name, None)
def _clear_vllm_modules():
for module_name in list(sys.modules):
if _is_vllm_name(module_name):
sys.modules.pop(module_name, None)
def disable_broken_vllm(error = None):
"""Disable vLLM dynamically when its shared library is ABI-broken."""
global VLLM_BROKEN
if VLLM_BROKEN:
_install_vllm_blocker()
return True
failure = error
if failure is None:
try:
if importlib.util.find_spec("vllm") is None:
return False
except Exception:
return False
try:
import vllm # noqa: F401
return False
except Exception as import_error:
failure = import_error
if not _is_broken_vllm_error(failure):
return False
VLLM_BROKEN = True
_clear_vllm_modules()
_install_vllm_blocker()
cuda_msg = _get_vllm_cuda_mismatch_message(failure)
if cuda_msg:
logger.warning(cuda_msg)
else:
logger.warning(
"Unsloth: Detected broken vLLM binary extension; "
"disabling vLLM imports and continuing import.\n"
"Please reinstall via `uv pip install unsloth vllm torchvision torchaudio "
"--torch-backend=auto`."
)
return True
def _disable_transformers_causal_conv1d():
try:
import transformers.utils.import_utils as tf_import_utils
except Exception:
return
if hasattr(tf_import_utils, "is_causal_conv1d_available"):
tf_import_utils.is_causal_conv1d_available = lambda: False
for attr_name in (
"_causal_conv1d_available",
"_is_causal_conv1d_available",
):
if hasattr(tf_import_utils, attr_name):
setattr(tf_import_utils, attr_name, False)
def disable_broken_causal_conv1d():
"""Disable causal_conv1d dynamically when its shared library is ABI-broken.
This mirrors Unsloth's FlashAttention fallback behavior: if importing causal_conv1d
fails with a known binary symbol error, we disable it at startup so model imports do
not hard-fail.
"""
global CAUSAL_CONV1D_BROKEN
if CAUSAL_CONV1D_BROKEN:
_install_causal_conv1d_blocker()
_disable_transformers_causal_conv1d()
return
try:
if importlib.util.find_spec("causal_conv1d") is None:
return
except Exception:
return
try:
import causal_conv1d # noqa: F401
return
except Exception as error:
if not _is_broken_causal_conv1d_error(error):
return
CAUSAL_CONV1D_BROKEN = True
_clear_causal_conv1d_modules()
_install_causal_conv1d_blocker()
_disable_transformers_causal_conv1d()
print(
"Unsloth: Detected broken causal_conv1d binary; "
"disabling causal_conv1d fast path and continuing import."
)