unsloth/unsloth/models/loader.py
Daniel Han 52a9601032
Keep import unsloth working when bitsandbytes is absent (#7502)
* Keep `import unsloth` working when bitsandbytes is absent

device_type.py already prints "bitsandbytes is not installed - 4bit QLoRA
unallowed, but 16bit and full finetuning works" and clears
ALLOW_BITSANDBYTES / ALLOW_PREQUANTIZED_MODELS, but the import chain then
hard-required the module anyway, so `import unsloth` raised instead.

#7354 made this reachable: the gfx906 install path uninstalls the generic
bitsandbytes wheel (no gfx906 kernels in it), which leaves an MI50 / Radeon VII
host unable to import unsloth at all, not on the 16bit path the message
promises.

- kernels/utils.py: guard the bnb import; bind get_ptr and the five 4bit ctypes
  handles to a stub that raises a clear message if a 4bit path is entered.
  HAS_CUDA_STREAM stays False, which is the correct route.
- save.py, models/granite.py: guard Bnb_Linear4bit and peft's Linear4bit
  (peft exports it only when bnb imported cleanly) with placeholder classes.
  Both names only feed isinstance checks, so nothing matching is exact.
- _gpu_init.py: same degradation on the xpu branch as the cuda branch above.

Verified on a Strix Halo (gfx1151, DEVICE_TYPE=hip, torch 2.11.0+rocm7.13.0)
by blocking bitsandbytes with sys.modules["bitsandbytes"] = None, so
find_spec returns None and the import raises exactly as when the package is
absent. Before: ModuleNotFoundError at kernels/utils.py:136. After: import
succeeds, FastLanguageModel/FastModel import, ALLOW_BITSANDBYTES=False,
ALLOW_PREQUANTIZED=False, and the 4bit stub raises with the real cause. With
bitsandbytes present, every binding is unchanged.

New test walks the `import unsloth` module graph with ast and fails on any
unguarded bitsandbytes (or peft Linear4bit) import; verified it catches the
old code. Targeted suites: 702 passed, 18 skipped.

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

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

* Address the review on #7502: zoo coupling, non-hip flags, py3.9 collection

Three findings, each reproduced first and negative-controlled after.

1. The fix still needed an unreleased unsloth_zoo (P1). save.py imported
   unsloth_zoo.saving_utils at module scope, and any zoo without the companion
   #953 fix imports bitsandbytes there, so `import unsloth` kept failing for a
   dependency set pyproject.toml allows. Raising the floor was not an option:
   PyPI's newest zoo is 2026.7.6 and #953 is merged but unreleased, so a bump
   would break every install today. Both names it pulled in are used only inside
   functions, so the import is now lazy at those two call sites, matching what
   determine_base_model_source in the same file already does. Verified against a
   real pre-#953 zoo checkout with bitsandbytes blocked: import succeeds, and
   restoring the eager import reproduces the failure at saving_utils.py:70.
   This PR no longer depends on a zoo release.

2. Capability flags were only cleared on hip (P2). device_type.py probed
   bitsandbytes inside its DEVICE_TYPE == "hip" branch, so a cuda or xpu host
   without bnb imported fine but still reported ALLOW_BITSANDBYTES=True, and the
   default load_in_4bit=True path in models/loader.py would select a 4bit
   checkpoint before failing. Clear both flags whenever the module is absent, on
   every backend, via find_spec so a working install pays nothing. A cuda host
   with bnb blocked now reports False/False; with bnb present nothing changes.

3. The new test could not be collected on Python 3.9 (P2). `Path | None` is a
   PEP 604 union and requires-python still allows 3.9, so pytest raised
   TypeError at import. Added `from __future__ import annotations`. Checked in
   real uv venvs on 3.9, 3.10 and 3.13: 2 passed each; removing the future
   import reproduces "unsupported operand type(s) for |" on 3.9 only.

The xpu branch in _gpu_init.py needs no separate flag handling now that the
probe is backend-independent.

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

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

* Address the second review on #7502: guarded probe, and 8bit in the same guard

1. The capability probe used find_spec while the fallbacks in kernels/utils.py
   and _gpu_init.py treat any import failure as unavailable, so an installed but
   unusable wheel would leave ALLOW_BITSANDBYTES true while the kernels had
   already bound the stub. Probe with the same guarded import instead, so all
   three agree by construction. No new cost on any path: _gpu_init.py already
   imports bnb before device_type is reached on cuda, and device_type's own hip
   block imports it a few lines later.

   Worth recording that the state this prevents is currently unreachable for an
   unrelated reason: a broken wheel takes `import unsloth` down earlier, in
   transformers/integrations/bitsandbytes.py:20 via
   unsloth_zoo/patching_utils.py:680, whichever exception it raises (OSError also
   escapes the zoo moe_utils `except ImportError`). So this is correctness for
   when those imports get guarded, not an observable fix today.

2. Both loader guards printed for load_in_4bit or load_in_8bit but only cleared
   load_in_4bit, so an explicit load_in_8bit=True survived and reached
   Transformers, which builds the bnb quantizer and fails there. Clear both. The
   message no longer says AMD either: the flag now goes false whenever bnb is
   unusable on any backend.

Tests: the probe must not use find_spec, and an ast walk requires every
ALLOW_BITSANDBYTES guard in loader.py to clear both flags, so a third guard
cannot be added with the same omission. Dropping either fix reddens them (1 and
2 failures respectively). 4 passed on 3.9, 3.13 and the ROCm venv; absent and
healthy bnb both stay consistent across hip and cuda.

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

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

* Drop the importlib import left over from the find_spec probe on #7502

* Address the third review on #7502: exact-name bypass and a forwarded bnb config

Both findings hold up, so both are fixed.

1. use_exact_model_name=True skipped the guard entirely. load_in_4bit defaults
   to True, so on a host without bitsandbytes
   FastLanguageModel.from_pretrained(name, use_exact_model_name=True) kept 4bit
   set and failed downstream. That option suppresses repo-name remapping and
   cannot make bitsandbytes available, so it has no business gating a capability
   check. Ungated at both sites.

2. A user-supplied quantization_config survived the fallback. It sets
   load_in_4bit/8bit at the top of from_pretrained and stays in kwargs, so
   clearing the local flags still let Transformers rebuild the bnb quantizer.
   Now dropped as part of the fallback.

One correction to the second suggestion: it cannot be dropped whenever the
fallback runs. quantization_config also carries GPTQ, AWQ, fp8 and torchao
configs, which have nothing to do with bitsandbytes and must reach the loader
untouched. The pop is gated on the config actually requesting load_in_4bit or
load_in_8bit, reusing the same dict/attr probe from the top of the function.

Behaviour, exercising the real guard block against synthetic inputs with
use_exact_model_name=True and bnb unusable:

  default 4bit, no cfg          4bit=False 8bit=False
  explicit 8bit, no cfg         4bit=False 8bit=False
  BitsAndBytesConfig(4bit/8bit) 4bit=False 8bit=False  config dropped
  dict bnb config               4bit=False 8bit=False  config dropped
  GPTQ config                   4bit=False 8bit=False  config SURVIVES
  fp8 dict                      4bit=False 8bit=False  config SURVIVES

Nothing changes when bitsandbytes works: the whole block is inside
`if not ALLOW_BITSANDBYTES`.

Tests: an ast walk requires neither guard to reference use_exact_model_name in
its test, and requires each to pop quantization_config behind a _wants_bnb
check, so an unconditional pop fails too. Re-gating one guard or removing one
pop reddens a test each. 6 passed on 3.9, 3.13 and the ROCm venv.

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

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

* Address the fourth review on #7502: FastModel never reached the 16bit path

Both findings are real, and the second one meant this PR did not actually
deliver what it advertises for FastModel or vision loads. Reproduced first.

1. patch_compiling_bitsandbytes() ran unguarded at the top of
   FastModel.from_pretrained, and unsloth_zoo's copy imports bitsandbytes
   unconditionally (patching_utils.py:40). So every FastModel call on a
   bnb-less host died there, whatever the arguments:

     FastModel(load_in_16bit=True)    -> ModuleNotFoundError at patching_utils.py:40
     FastModel(full_finetuning=True)  -> ModuleNotFoundError at patching_utils.py:40

   The FastLanguageModel path already wraps this call in try/except with a
   warning, and its comment even says "Mirror FastModel" - FastModel was the
   unwrapped one. Wrapped it the same way, so behaviour is unchanged wherever
   bitsandbytes imports.

2. The mode-exclusivity check ran before the capability fallback. load_in_4bit
   defaults to True, so load_in_16bit=True made
   int(load_in_4bit) + int(load_in_16bit) == 2 and raised "Can only load in 4bit
   or 8bit or 16bit" before the fallback could clear the unavailable 4bit
   request. Moved the fallback ahead of that check.

After both, the same three calls get past every bitsandbytes gate and reach
model resolution, failing only on the deliberately fake repo name used by the
probe. Nothing changes when bitsandbytes works: the fallback is still inside
`if not ALLOW_BITSANDBYTES`, and the wrapper only swallows an import that
previously crashed the load.

Tests: the mode check must be preceded by an ALLOW_BITSANDBYTES fallback in the
same function, and no call to patch_compiling_bitsandbytes may sit outside a
try. The ordering assertion is scoped to the enclosing function on purpose - my
first version compared line numbers file-wide, so the other loader's guard
satisfied it and the negative control passed when it should have failed. With
the scoping fixed, moving the fallback back after the mode check reddens it, as
does unwrapping the patch call. 8 passed on 3.9, 3.13 and the ROCm venv.

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

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

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-28 08:03:54 -07:00

2019 lines
92 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.
from ._utils import (
_prepare_model_for_qat,
is_bfloat16_supported,
is_vLLM_available,
HAS_FLASH_ATTENTION,
HAS_FLASH_ATTENTION_SOFTCAPPING,
USE_MODELSCOPE,
get_transformers_model_type,
hf_login,
# Single source of truth is _utils.py; re-exported here so callers doing
# `from unsloth.models.loader import DISABLE_SDPA_MODEL_NAMES` keep working and so
# _is_sdpa_excluded (in _utils) can honor it without a loader -> _utils cycle.
DISABLE_SDPA_MODEL_NAMES,
)
from .granite import FastGraniteModel
from .llama import FastLlamaModel, logger
from .mistral import FastMistralModel
from .qwen2 import FastQwen2Model
from .qwen3 import FastQwen3Model
from .qwen3_moe import FastQwen3MoeModel
from .cohere import FastCohereModel
from transformers import AutoConfig
from transformers import __version__ as transformers_version
from peft import PeftConfig, PeftModel
from .loader_utils import (
_exclude_rope_inv_freq_from_ddp,
_get_fp8_mode_and_check_settings,
_offline_quantize_to_fp8,
_tag_model_with_fp8_torchao_config,
get_model_name,
prepare_device_map,
_offline_aware_load,
_resolve_checkpoint_tokenizer_name,
_is_offline_related_error,
)
import os, contextlib, sys
try:
from huggingface_hub import get_token
except:
try:
from huggingface_hub.utils import get_token
except:
# For older versions of huggingface_hub
from huggingface_hub.utils._token import get_token
import importlib.util
from ..device_type import (
is_hip,
get_device_type,
DEVICE_TYPE,
DEVICE_TYPE_TORCH,
DEVICE_COUNT,
ALLOW_PREQUANTIZED_MODELS,
ALLOW_BITSANDBYTES,
)
# https://github.com/huggingface/transformers/pull/26037 allows 4 bit loading!
from unsloth_zoo.utils import Version, _get_dtype
from unsloth_zoo.hf_utils import dtype_from_config
from unsloth_zoo.tiled_mlp import patch_tiled_mlp
transformers_version = Version(transformers_version)
SUPPORTS_FOURBIT = transformers_version >= Version("4.37")
SUPPORTS_GEMMA = transformers_version >= Version("4.38")
SUPPORTS_GEMMA2 = transformers_version >= Version("4.42")
SUPPORTS_LLAMA31 = transformers_version >= Version("4.43.2")
SUPPORTS_LLAMA32 = transformers_version > Version("4.45.0")
SUPPORTS_GRANITE = transformers_version >= Version("4.46.0")
SUPPORTS_QWEN3 = transformers_version >= Version("4.50.3")
SUPPORTS_QWEN3_MOE = transformers_version >= Version("4.50.3")
SUPPORTS_FALCON_H1 = transformers_version >= Version("4.53.0")
SUPPORTS_GEMMA3N = transformers_version >= Version("4.53.0")
SUPPORTS_GPTOSS = transformers_version >= Version("4.55.0")
SUPPORTS_GEMMA4 = transformers_version >= Version("5.5.0")
# Transformers v5 meta-device loading corrupts non-persistent buffers (inv_freq).
# See _fix_rope_inv_freq() below for details.
_NEEDS_ROPE_FIX = transformers_version >= Version("5.0.0")
if SUPPORTS_GEMMA:
from .gemma import FastGemmaModel
if SUPPORTS_GEMMA2:
from .gemma2 import FastGemma2Model
if SUPPORTS_FALCON_H1:
from .falcon_h1 import FastFalconH1Model
import torch
from ._utils import (
patch_compiling_bitsandbytes,
patch_model_and_tokenizer,
prepare_model_for_kbit_training,
apply_unsloth_gradient_checkpointing,
patch_compiled_autograd,
process_vision_info,
unsloth_compile_transformers,
fast_inference_setup,
_get_text_only_config,
resolve_model_class,
_is_family_text_decoder,
_apply_text_only_key_mapping,
set_task_config_attr,
maybe_prefetch_hf_snapshot,
)
# Source of truth is unsloth_zoo.model_lists. Re-exported so callers doing
# `from unsloth.models.loader import FORCE_FLOAT32` keep working. The fallback
# list is also unioned in so a newer unsloth still forces float32 for these
# archs when paired with an older unsloth_zoo that predates them (upgrade skew).
_FORCE_FLOAT32_FALLBACK = [
"gemma3,", # Add comma bc gemma3 will match gemma3n
"gemma3text", # Gemma3TextModel (EmbeddingGemma, standalone text-only Gemma3)
"gemma3n",
"gemma4", # Gemma4 (gemma4 / gemma4_text): float16 NaNs grad norms in the backward
"glm4_moe", # GLM-4.x MoE (glm4_moe / glm4_moe_lite): float16 NaNs grad norms
"gpt_oss",
"qwen3_5", # Qwen3.5 GDN layers produce NaN grad norms in float16 training
"qwen3_moe", # Qwen3-MoE (Qwen3-30B-A3B): float16 NaNs grad norms in the backward
]
try:
from unsloth_zoo import FORCE_FLOAT32 as _ZOO_FORCE_FLOAT32
FORCE_FLOAT32 = list(_ZOO_FORCE_FLOAT32)
except ImportError:
FORCE_FLOAT32 = []
for _mt in _FORCE_FLOAT32_FALLBACK:
if not any(_mt in _entry for _entry in FORCE_FLOAT32):
FORCE_FLOAT32.append(_mt)
global DISABLE_COMPILE_MODEL_NAMES
# Must be alphabetically sorted for each entry
def _strip_unsloth_bnb_4bit_suffix(model_name: str) -> str:
"""Remove Unsloth 4bit suffixes without lowercasing (HF cache dirs are case-sensitive)."""
s = model_name
for suffix in ("-unsloth-bnb-4bit", "-bnb-4bit"):
if len(s) >= len(suffix) and s.lower().endswith(suffix.lower()):
s = s[: -len(suffix)]
return s
def _config_get(
config,
field_name,
default = None,
):
if isinstance(config, dict):
return config.get(field_name, default)
return getattr(config, field_name, default)
def _config_diff(config):
if isinstance(config, dict):
return config
to_diff_dict = getattr(config, "to_diff_dict", None)
if callable(to_diff_dict):
try:
diff = to_diff_dict()
if isinstance(diff, dict):
return diff
except Exception:
pass
return {}
def _has_sequence_classification_architecture(config):
architectures = _config_get(config, "architectures", None) or []
return any(str(arch).endswith("ForSequenceClassification") for arch in architectures)
def _get_user_task_config_attrs(user_config):
if user_config is None:
return {}
diff = _config_diff(user_config)
attrs = {}
for key in ("id2label", "label2id", "problem_type"):
if key in diff:
attrs[key] = _config_get(user_config, key, diff.get(key))
if isinstance(user_config, dict) and "num_labels" in user_config:
attrs["num_labels"] = user_config["num_labels"]
elif _has_sequence_classification_architecture(user_config):
num_labels = _config_get(user_config, "num_labels", None)
if num_labels is not None:
attrs["num_labels"] = num_labels
elif "id2label" in attrs:
try:
attrs["num_labels"] = len(attrs["id2label"])
except TypeError:
pass
return attrs
DISABLE_COMPILE_MODEL_NAMES = [
"aya_vision",
"modernbert",
"granite,llava_next", # Granite-vision 3
]
# Architectures with gated-deltanet (linear attention) layers. Unsloth bundles the
# flash-linear-attention Triton kernels (unsloth_zoo/_vendored/fla), so no install is
# needed; transformers uses the much slower pure PyTorch path only when they can't be enabled.
FLA_MODEL_TYPE_PREFIXES = ("qwen3_next", "qwen3_5", "kimi_linear", "olmo_hybrid")
_fla_advised = False
def _maybe_advise_fla_install(model_types):
"""One-time note when a gated-deltanet model loads without the fast kernels.
The kernels ship with Unsloth (no install needed); this fires only when they
could not be enabled on this platform (e.g. no CUDA, torch < 2.7 or
triton < 3.3), i.e. exactly when transformers uses the slow pure PyTorch path.
"""
global _fla_advised
if _fla_advised:
return
if model_types is None:
return
if isinstance(model_types, str):
model_types = [model_types] # a lone string would otherwise iterate chars
try:
if not any(
isinstance(t, str) and t.startswith(FLA_MODEL_TYPE_PREFIXES) for t in model_types
):
return
from transformers.utils.import_utils import is_flash_linear_attention_available
if is_flash_linear_attention_available():
return # bundled (or user-installed) fast kernels are active
except Exception:
return
_fla_advised = True
print(
"Unsloth: This model uses gated-deltanet linear attention layers. Unsloth\n"
"bundles the flash-linear-attention kernels, but they could not be enabled\n"
"on this setup (they need CUDA with torch >= 2.7 and triton >= 3.3), so\n"
"transformers will use a slower pure PyTorch path."
)
def _fix_rope_inv_freq(model):
"""Fix inv_freq corruption caused by transformers v5 meta-device loading.
v5 inits on meta then replaces all non-persistent buffers with uninitialized
memory. Vanilla restores inv_freq via _init_weights() (needs original_inv_freq),
but Unsloth rotary classes lack that attr, so inv_freq stays corrupted -> wrong
positional encodings and 5-11x higher training loss. Here we recompute inv_freq
from base/dim, apply scaling, and rebuild cos/sin caches. No-op on v4.
"""
if not _NEEDS_ROPE_FIX:
return model
for name, module in model.named_modules():
# Unsloth's LlamaRotaryEmbedding and subclasses (Extended, LinearScaling,
# Granite). Native v5 rotary classes (Gemma3, etc.) have original_inv_freq
# which v5's _init_weights() uses to restore inv_freq, so they are fine.
if (
hasattr(module, "inv_freq")
and hasattr(module, "base")
and hasattr(module, "dim")
and hasattr(module, "_apply_inv_freq_scaling")
and hasattr(module, "multi_gpu_cos_cached")
):
if hasattr(module, "_unsloth_recompute_inv_freq"):
# Restore config scaling (llama3/yarn); unscaled here broke v5.
inv_freq = module._unsloth_recompute_inv_freq()
else:
inv_freq = 1.0 / (
module.base
** (
torch.arange(0, module.dim, 2, dtype = torch.int64, device = "cpu").float()
/ module.dim
)
)
inv_freq = module._apply_inv_freq_scaling(inv_freq)
module.inv_freq = inv_freq
for device_idx in range(len(module.multi_gpu_cos_cached)):
if module.multi_gpu_cos_cached[device_idx] is not None:
module._set_cos_sin_cache(
seq_len = module.current_rope_size,
device = torch.device(device_idx),
dtype = torch.get_default_dtype(),
)
# LongRopeRotaryEmbedding (Phi-3.5 style with short_inv_freq + long_inv_freq)
elif (
hasattr(module, "short_inv_freq")
and hasattr(module, "long_inv_freq")
and hasattr(module, "base")
and hasattr(module, "dim")
):
config = getattr(model, "config", None)
rope_scaling = getattr(config, "rope_scaling", None) if config else None
if rope_scaling is not None:
short_factor = rope_scaling.get("short_factor", None)
long_factor = rope_scaling.get("long_factor", None)
if short_factor is not None and long_factor is not None:
inv_freq_shape = (
torch.arange(0, module.dim, 2, dtype = torch.int64, device = "cpu").float()
/ module.dim
)
sf = torch.tensor(short_factor, device = "cpu", dtype = torch.float32)
lf = torch.tensor(long_factor, device = "cpu", dtype = torch.float32)
module.short_inv_freq = 1.0 / (sf * module.base**inv_freq_shape)
module.long_inv_freq = 1.0 / (lf * module.base**inv_freq_shape)
dtype = torch.bfloat16 if is_bfloat16_supported() else torch.float16
t = torch.arange(
module.original_max_position_embeddings,
device = module.short_inv_freq.device,
dtype = torch.int64,
).float()
freqs = torch.outer(t, module.short_inv_freq)
emb = torch.cat((freqs, freqs), dim = -1)
for device_idx in range(len(module.multi_gpu_short_cos_cached)):
if module.multi_gpu_short_cos_cached[device_idx] is not None:
device_obj = torch.device(device_idx)
module.multi_gpu_short_cos_cached[device_idx] = (
emb.cos() * module.scaling_factor
).to(dtype = dtype, device = device_obj, non_blocking = True)
module.multi_gpu_short_sin_cached[device_idx] = (
emb.sin() * module.scaling_factor
).to(dtype = dtype, device = device_obj, non_blocking = True)
return model
class FastLanguageModel(FastLlamaModel):
@staticmethod
@_offline_aware_load
def from_pretrained(
model_name = "unsloth/Llama-3.2-1B-Instruct",
max_seq_length = 2048,
dtype = None,
load_in_4bit = True, # 4bit QLoRA
load_in_8bit = False, # 8bit LoRA
load_in_16bit = False, # 16bit LoRA
full_finetuning = False,
token = None,
device_map = "sequential",
rope_scaling = None,
fix_tokenizer = True,
trust_remote_code = False,
use_gradient_checkpointing = "unsloth",
resize_model_vocab = None,
revision = None,
use_exact_model_name = False,
offload_embedding = False,
float32_mixed_precision = None, # Forces float32 mixed precision
fast_inference = False, # uses vLLM
gpu_memory_utilization = 0.5,
float8_kv_cache = False,
random_state = 3407,
max_lora_rank = 64,
disable_log_stats = True,
qat_scheme = None,
load_in_fp8 = False, # fp8 LoRA (True, False, 'block')
unsloth_tiled_mlp = False,
text_only = False, # Skip vision/audio towers and load only the text decoder
*args,
**kwargs,
):
# Respect user-provided quantization_config (e.g. BitsAndBytesConfig)
quantization_config = kwargs.get("quantization_config", None)
if quantization_config is not None:
if isinstance(quantization_config, dict):
q_load_in_4bit = quantization_config.get("load_in_4bit", False)
q_load_in_8bit = quantization_config.get("load_in_8bit", False)
else:
q_load_in_4bit = getattr(quantization_config, "load_in_4bit", False)
q_load_in_8bit = getattr(quantization_config, "load_in_8bit", False)
if q_load_in_4bit:
load_in_4bit = True
load_in_8bit = False
if q_load_in_8bit:
load_in_8bit = True
load_in_4bit = False
# Login to allow private models
token = hf_login(token)
# Align dtype with bnb_4bit_compute_dtype if provided and dtype is unset.
if dtype is None and quantization_config is not None:
bnb_compute_dtype = None
if isinstance(quantization_config, dict):
if quantization_config.get("load_in_4bit", False):
bnb_compute_dtype = quantization_config.get("bnb_4bit_compute_dtype", None)
else:
if getattr(quantization_config, "load_in_4bit", False):
bnb_compute_dtype = getattr(quantization_config, "bnb_4bit_compute_dtype", None)
if isinstance(bnb_compute_dtype, str):
bnb_compute_dtype = getattr(torch, bnb_compute_dtype, None)
if isinstance(bnb_compute_dtype, torch.dtype):
dtype = bnb_compute_dtype
# Distributed-safe device placement for quantized models.
# In multi-GPU (torchrun), each rank must load the model on its own device
# to avoid Accelerate device relocation errors with quantized weights.
is_quantized = load_in_4bit or load_in_8bit or load_in_fp8
if is_quantized and isinstance(device_map, str):
distributed_device_map, is_dist = prepare_device_map()
if is_dist:
device_map = distributed_device_map
# @_offline_aware_load already forced offline when needed; delegations inherit it.
if load_in_8bit or full_finetuning or qat_scheme is not None:
return FastModel.from_pretrained(
model_name = model_name,
max_seq_length = max_seq_length,
dtype = dtype,
load_in_4bit = load_in_4bit,
load_in_8bit = load_in_8bit,
load_in_16bit = load_in_16bit,
full_finetuning = full_finetuning,
token = token,
device_map = device_map,
rope_scaling = rope_scaling, # [TODO] No effect
fix_tokenizer = fix_tokenizer, # [TODO] No effect
trust_remote_code = trust_remote_code,
use_gradient_checkpointing = use_gradient_checkpointing,
resize_model_vocab = resize_model_vocab, # [TODO] No effect
revision = revision,
return_logits = False, # Return logits
fullgraph = True, # No graph breaks
use_exact_model_name = use_exact_model_name,
offload_embedding = offload_embedding,
float32_mixed_precision = float32_mixed_precision,
# Pass vLLM/inference parameters
fast_inference = fast_inference,
gpu_memory_utilization = gpu_memory_utilization,
float8_kv_cache = float8_kv_cache,
random_state = random_state,
max_lora_rank = max_lora_rank,
disable_log_stats = disable_log_stats,
qat_scheme = qat_scheme,
load_in_fp8 = load_in_fp8,
unsloth_tiled_mlp = unsloth_tiled_mlp,
text_only = text_only,
*args,
**kwargs,
)
if isinstance(dtype, str) and dtype in ["float16", "bfloat16"]:
dtype = getattr(torch, dtype)
assert (
dtype is None
or dtype == torch.float16
or dtype == torch.bfloat16
or dtype == torch.float32
)
if fast_inference:
if importlib.util.find_spec("vllm") is None:
raise ImportError(
"Unsloth: Please install vLLM before enabling `fast_inference`!\n"
"You can do this in a terminal via `pip install vllm`"
)
if DEVICE_TYPE_TORCH == "cuda":
for i in range(DEVICE_COUNT):
# [TODO] DGX Spark vLLM breaks
if "NVIDIA GB10" in str(torch.cuda.get_device_name(i)).upper():
print(
"Unsloth: DGX Spark detected - `fast_inference=True` is currently broken as of January 2026.\n"
"Defaulting to native Unsloth inference."
)
fast_inference = False
break
# bitsandbytes unusable (absent, or unstable as on some AMD stacks). This is
# a capability check, so it is not gated on use_exact_model_name: that only
# suppresses repo-name remapping and cannot make bitsandbytes available.
if not ALLOW_BITSANDBYTES:
# A user-supplied config sets load_in_4bit/8bit above and is forwarded
# in kwargs, so clearing the flags alone still rebuilds the bnb
# quantizer downstream. Only drop it when it asks for bnb: a GPTQ /
# AWQ / fp8 / torchao config must pass through untouched.
_quant_cfg = kwargs.get("quantization_config", None)
if isinstance(_quant_cfg, dict):
_wants_bnb = bool(
_quant_cfg.get("load_in_4bit", False) or _quant_cfg.get("load_in_8bit", False)
)
elif _quant_cfg is not None:
_wants_bnb = bool(
getattr(_quant_cfg, "load_in_4bit", False)
or getattr(_quant_cfg, "load_in_8bit", False)
)
else:
_wants_bnb = False
if (
load_in_4bit
or load_in_8bit
or _wants_bnb
or model_name.lower().endswith("-bnb-4bit")
):
print(
"Unsloth: `bitsandbytes` is unavailable here - disabling 4bit/8bit. "
"16bit LoRA and full finetuning still work."
)
# 8bit is bitsandbytes too: leaving either set sends the request on to
# Transformers, which builds the bnb quantizer and fails there.
load_in_4bit = False
load_in_8bit = False
if _wants_bnb:
kwargs.pop("quantization_config", None)
# Find FP8, BnB 4bit, other mapped names
old_model_name = model_name
fp8_mode = None
if not use_exact_model_name:
new_model_name = get_model_name(
model_name,
load_in_4bit = load_in_4bit,
load_in_fp8 = load_in_fp8,
token = token,
trust_remote_code = trust_remote_code,
)
if new_model_name is None and load_in_fp8 != False:
fp8_mode = _get_fp8_mode_and_check_settings(
load_in_fp8,
fast_inference,
full_finetuning,
load_in_4bit,
load_in_8bit,
load_in_16bit,
)
model_name = _offline_quantize_to_fp8(model_name, fp8_mode, text_only = text_only)
else:
assert new_model_name is not None
model_name = new_model_name
# If mapper resolved to a pre-quantized FP8 model, disable
# on-the-fly quantization to avoid double quantization
if load_in_fp8 != False and new_model_name != old_model_name:
load_in_fp8 = False
# Check if pre-quantized models are allowed
# AMD Instinct GPUs need blocksize = 128 on bitsandbytes < 0.49.2 (our pre-quants use blocksize = 64)
if not ALLOW_PREQUANTIZED_MODELS and model_name.lower().endswith(
("-unsloth-bnb-4bit", "-bnb-4bit")
):
model_name = _strip_unsloth_bnb_4bit_suffix(model_name)
# '-bf16' hub repos load bf16; a local dir keeps the requested quant unless 16bit is set
if model_name.lower().endswith("-bf16") and (
load_in_16bit or not os.path.isdir(os.path.expanduser(model_name))
):
load_in_4bit = False
load_in_8bit = False
load_in_fp8 = False
load_in_16bit = True
if USE_MODELSCOPE and not os.path.exists(model_name):
from modelscope import snapshot_download
model_name = snapshot_download(model_name)
# First check if it's a normal model via AutoConfig
from huggingface_hub.utils import (
disable_progress_bars,
enable_progress_bars,
are_progress_bars_disabled,
)
was_disabled = are_progress_bars_disabled()
disable_progress_bars()
autoconfig_error = None
peft_error = None
autoconfig_exc = None
peft_exc = None
model_config = None
peft_config = None
local_files_only = kwargs.get("local_files_only", False)
try:
model_config = AutoConfig.from_pretrained(
model_name,
token = token,
revision = revision,
trust_remote_code = trust_remote_code,
local_files_only = local_files_only,
)
is_model = True
except ImportError:
raise
except Exception as error:
autoconfig_error = str(error)
autoconfig_exc = error
if "architecture" in autoconfig_error:
if "qwen3_5" in autoconfig_error:
raise ImportError(
f"Unsloth: Your transformers version of {transformers_version} does not support Qwen3.5.\n"
f"The minimum required version is 5.2.0.\n"
f'Try `pip install --upgrade "transformers>=5.2.0"`\n'
f"to obtain the latest transformers build, then restart this session."
)
raise ValueError(
f"`{model_name}` is not supported yet in `transformers=={transformers_version}`.\n"
f"Please update transformers via `pip install --upgrade transformers` and try again."
)
is_model = False
try:
peft_config = PeftConfig.from_pretrained(
model_name,
token = token,
revision = revision,
trust_remote_code = trust_remote_code,
local_files_only = local_files_only,
)
is_peft = True
except ImportError:
raise
except Exception as error:
peft_error = str(error)
peft_exc = error
if "architecture" in peft_error:
raise ValueError(
f"`{model_name}` is not supported yet in `transformers=={transformers_version}`.\n"
f"Please update transformers via `pip install --upgrade transformers` and try again."
)
is_peft = False
# Old transformers versions check
both_exist = (is_model and is_peft) and not SUPPORTS_LLAMA32
# Error out if both LoRA and normal model config exists.
if both_exist:
raise RuntimeError(
"Unsloth: Your repo has a LoRA adapter and a base model.\n"
"You have 2 files `config.json` and `adapter_config.json`.\n"
"We must only allow one config file.\n"
"Please separate the LoRA and base models to 2 repos."
)
if not is_model and not is_peft:
error = autoconfig_error if autoconfig_error is not None else peft_error
# Old transformers version
if "rope_scaling" in error.lower() and not SUPPORTS_LLAMA31:
raise ImportError(
f"Unsloth: Your transformers version of {transformers_version} does not support new RoPE scaling methods.\n"
f"This includes Llama 3.1. The minimum required version is 4.43.2\n"
f'Try `pip install --upgrade "transformers>=4.43.2"`\n'
f"to obtain the latest transformers build, then restart this session."
)
# Create a combined error message showing both failures
combined_error = (
"Unsloth: Failed to load model. Both AutoConfig and PeftConfig loading failed.\n\n"
f"AutoConfig error: {autoconfig_error}\n\n"
f"PeftConfig error: {peft_error}\n\n"
)
# Chain an offline-related cause if either probe had one, so @_offline_aware_load
# still retries from cache (e.g. adapter repo: permanent AutoConfig 404 + transient PeftConfig).
_cause = next(
(
e
for e in (autoconfig_exc, peft_exc)
if e is not None and _is_offline_related_error(e)
),
autoconfig_exc or peft_exc,
)
raise RuntimeError(combined_error) from _cause
model_types = get_transformers_model_type(
peft_config if peft_config is not None else model_config,
trust_remote_code = trust_remote_code,
)
if len(model_types) == 1:
model_type = model_types[0]
else:
# Leave as tuple if more than one arch
model_type = model_types
# New transformers need to check manually.
if SUPPORTS_LLAMA32 and is_model and is_peft:
# Check if folder exists locally
if os.path.isdir(model_name):
exist_adapter_config = os.path.exists(
os.path.join(model_name, "adapter_config.json")
)
exist_config = os.path.exists(os.path.join(model_name, "config.json"))
both_exist = exist_adapter_config and exist_config
else:
# Both AutoConfig and PeftConfig loaded successfully from this
# remote repo, so both config.json and adapter_config.json
# definitely exist -- no need for an extra HfFileSystem network call.
both_exist = True
# Get base model for PEFT:
if is_peft:
# Check base model again for PEFT
model_name = peft_config.base_model_name_or_path
if not use_exact_model_name:
model_name = get_model_name(
model_name,
load_in_4bit = load_in_4bit,
load_in_fp8 = load_in_fp8,
token = token,
trust_remote_code = trust_remote_code,
)
# Check if pre-quantized models are allowed
# AMD Instinct GPUs need blocksize = 128 on bitsandbytes < 0.49.2 (our pre-quants use blocksize = 64)
if not ALLOW_PREQUANTIZED_MODELS and model_name.lower().endswith(
("-unsloth-bnb-4bit", "-bnb-4bit")
):
model_name = _strip_unsloth_bnb_4bit_suffix(model_name)
# '-bf16' hub repos load bf16; a local dir keeps the requested quant unless 16bit is set
if model_name.lower().endswith("-bf16") and (
load_in_16bit or not os.path.isdir(os.path.expanduser(model_name))
):
load_in_4bit = False
load_in_8bit = False
load_in_fp8 = False
load_in_16bit = True
model_config = AutoConfig.from_pretrained(
model_name,
token = token,
trust_remote_code = trust_remote_code,
local_files_only = local_files_only,
)
if not was_disabled:
enable_progress_bars()
if model_type == "llama":
scaling_type = None
if getattr(model_config, "rope_scaling", None) is not None:
scaling_type1 = model_config.rope_scaling.get("type", None)
scaling_type2 = model_config.rope_scaling.get("rope_type", None)
scaling_type = scaling_type1 if scaling_type1 is not None else scaling_type2
if scaling_type == "llama3" and not SUPPORTS_LLAMA31:
raise ImportError(
f"Unsloth: Your transformers version of {transformers_version} does not support Llama 3.1.\n"
f"The minimum required version is 4.43.2\n"
f'Try `pip install --upgrade "transformers>=4.43.2"`\n'
f"to obtain the latest transformers build, then restart this session."
)
dispatch_model = FastLlamaModel
elif model_type == "mistral":
dispatch_model = FastMistralModel
elif model_type == "gemma":
if not SUPPORTS_GEMMA:
raise ImportError(
f"Unsloth: Your transformers version of {transformers_version} does not support Gemma.\n"
f"The minimum required version is 4.38.\n"
f'Try `pip install --upgrade "transformers>=4.38"`\n'
f"to obtain the latest transformers build, then restart this session."
)
dispatch_model = FastGemmaModel
elif model_type == "gemma2":
if not SUPPORTS_GEMMA2:
raise ImportError(
f"Unsloth: Your transformers version of {transformers_version} does not support Gemma2.\n"
f"The minimum required version is 4.42.3.\n"
f'Try `pip install --upgrade "transformers>=4.42.3"`\n'
f"to obtain the latest transformers build, then restart this session."
)
# Also check for softcapping support in flash-attn which is faster!
if is_bfloat16_supported() and not HAS_FLASH_ATTENTION:
print(
"Unsloth: If you want to finetune Gemma 2, install flash-attn to make it faster!\n"
"To install flash-attn, do the below:\n"
'\npip install --no-deps --upgrade "flash-attn>=2.6.3"'
)
elif HAS_FLASH_ATTENTION and not HAS_FLASH_ATTENTION_SOFTCAPPING:
print(
"Unsloth: If you want to finetune Gemma 2, upgrade flash-attn to version 2.6.3 or higher!\n"
"Newer versions support faster and less memory usage kernels for Gemma 2's attention softcapping!\n"
"To update flash-attn, do the below:\n"
'\npip install --no-deps --upgrade "flash-attn>=2.6.3"'
)
dispatch_model = FastGemma2Model
elif model_type == "qwen2":
dispatch_model = FastQwen2Model
elif model_type == "qwen3": # or model_type == "qwen3_moe":
if not SUPPORTS_QWEN3 or not SUPPORTS_QWEN3_MOE:
raise ImportError(
f"Unsloth: Your transformers version of {transformers_version} does not support Qwen3.\n"
f"The minimum required version is 4.50.3.\n"
f'Try `pip install --upgrade "transformers>=4.50.3"`\n'
f"to obtain the latest transformers build, then restart this session."
)
dispatch_model = FastQwen3Model if model_type == "qwen3" else FastQwen3MoeModel
# elif model_type == "falcon_h1":
# dispatch_model = FastFalconH1Model
# if not SUPPORTS_FALCON_H1:
# raise ImportError(
# f"Unsloth: Your transformers version of {transformers_version} does not support FalconH1.\n"\
# f"The minimum required version is 4.50.3.\n"\
# f'Try `pip install --upgrade "transformers>=4.50.3"`\n'\
# f"to obtain the latest transformers build, then restart this session."\
# )
# Temporary disable optimized Cohere until errors match
# elif model_type == "cohere":
# dispatch_model = FastCohereModel
# Temporary disable optimized Granite until errors match
# elif model_type == "granite":
# dispatch_model = FastGraniteModel
else:
return FastModel.from_pretrained(
model_name = old_model_name,
max_seq_length = max_seq_length,
dtype = dtype,
load_in_4bit = load_in_4bit,
load_in_8bit = load_in_8bit,
load_in_16bit = load_in_16bit,
full_finetuning = full_finetuning,
token = token,
device_map = device_map,
rope_scaling = rope_scaling, # [TODO] No effect
fix_tokenizer = fix_tokenizer, # [TODO] No effect
trust_remote_code = trust_remote_code,
use_gradient_checkpointing = use_gradient_checkpointing,
resize_model_vocab = resize_model_vocab, # [TODO] No effect
revision = revision,
return_logits = False, # Return logits
fullgraph = True, # No graph breaks
use_exact_model_name = use_exact_model_name,
offload_embedding = offload_embedding,
float32_mixed_precision = float32_mixed_precision,
# Pass vLLM/inference parameters
fast_inference = fast_inference,
gpu_memory_utilization = gpu_memory_utilization,
float8_kv_cache = float8_kv_cache,
random_state = random_state,
max_lora_rank = max_lora_rank,
disable_log_stats = disable_log_stats,
qat_scheme = qat_scheme,
load_in_fp8 = load_in_fp8,
unsloth_tiled_mlp = unsloth_tiled_mlp,
text_only = text_only,
*args,
**kwargs,
)
# Apply gradient checkpointing with smart heuristics
use_gradient_checkpointing = apply_unsloth_gradient_checkpointing(
use_gradient_checkpointing, max_seq_length, dtype
)
# Keep the local checkpoint dir as tokenizer when self-sufficient (see _resolve_checkpoint_tokenizer_name).
tokenizer_name = _resolve_checkpoint_tokenizer_name(old_model_name, kwargs)
if fast_inference:
fast_inference, model_name = fast_inference_setup(model_name, model_config)
load_in_4bit_kwargs = load_in_4bit
load_in_8bit_kwargs = load_in_8bit
if quantization_config is not None and not fast_inference:
load_in_4bit_kwargs = False
load_in_8bit_kwargs = False
# Mirror FastModel: bitsandbytes < 0.46.0 needs dynamo disabled.
# Best effort: never crash the load (old unsloth_zoo without the
# zoo #710 fix raises NameError here on Python 3.13).
try:
patch_compiling_bitsandbytes()
except Exception as e:
print(f"Unsloth: Could not patch bitsandbytes for torch.compile - {e}")
model, tokenizer = dispatch_model.from_pretrained(
model_name = model_name,
max_seq_length = max_seq_length,
dtype = _get_dtype(dtype),
load_in_4bit = load_in_4bit_kwargs,
token = token,
device_map = device_map,
rope_scaling = rope_scaling,
fix_tokenizer = fix_tokenizer,
model_patcher = dispatch_model,
tokenizer_name = tokenizer_name,
trust_remote_code = trust_remote_code,
revision = revision if not is_peft else None,
fast_inference = fast_inference,
gpu_memory_utilization = gpu_memory_utilization,
float8_kv_cache = float8_kv_cache,
random_state = random_state,
max_lora_rank = max_lora_rank,
disable_log_stats = disable_log_stats,
load_in_fp8 = load_in_fp8,
*args,
**kwargs,
)
if resize_model_vocab is not None:
model.resize_token_embeddings(resize_model_vocab)
# In case the model supports tagging, add the unsloth tag.
if hasattr(model, "add_model_tags"):
model.add_model_tags(
[
"unsloth",
]
)
if hasattr(tokenizer, "add_model_tags"):
tokenizer.add_model_tags(
[
"unsloth",
]
)
if load_in_4bit:
# Fix up bitsandbytes config, but respect user-provided quantization_config
if quantization_config is None:
# `load_in_4bit` is the requested flag, not the effective one: a non-bnb
# checkpoint (MXFP4/gptq/awq) had bnb disabled by check_and_disable, so stamping a
# synthetic bnb config would corrupt its real one. Only stamp bnb/unquantized.
try:
from unsloth_zoo.utils import get_quant_type
_stamp_bnb = get_quant_type(model.config) in (None, "bitsandbytes")
except Exception:
_stamp_bnb = True
if _stamp_bnb:
compute_dtype = dtype_from_config(model.config)
quantization_config = {
# Sometimes compute_dtype is not a string!!
"bnb_4bit_compute_dtype": compute_dtype,
"bnb_4bit_quant_type": "nf4",
"bnb_4bit_use_double_quant": True,
"llm_int8_enable_fp32_cpu_offload": False,
"llm_int8_has_fp16_weight": False,
"llm_int8_skip_modules": None,
"llm_int8_threshold": 6.0,
"load_in_4bit": True,
"load_in_8bit": False,
"quant_method": "bitsandbytes",
}
model.config.update({"quantization_config": quantization_config})
else:
if hasattr(quantization_config, "to_dict"):
model.config.update({"quantization_config": quantization_config.to_dict()})
elif isinstance(quantization_config, dict):
model.config.update({"quantization_config": quantization_config})
if load_in_fp8 != False:
_tag_model_with_fp8_torchao_config(model, fp8_mode)
if is_peft:
# From https://github.com/huggingface/peft/issues/184
# Now add PEFT adapters
# Warm the adapter repo: PeftModel downloads it in-process and can hang on Xet.
_prefetched = maybe_prefetch_hf_snapshot(
old_model_name,
token = token,
revision = revision,
cache_dir = kwargs.get("cache_dir"),
local_files_only = local_files_only,
# Adapter always loads in-process via PeftModel, so warm it even under fast_inference.
fast_inference = False,
force_download = kwargs.get("force_download", False),
# Leave use_safetensors auto (inheriting base format could skip a safetensors-only
# adapter). adapter_only restricts the warm to the adapter files + root aux.
adapter_only = True,
)
# Child did the forced download; clear the flag so the load reuses the warm cache.
if _prefetched and kwargs.get("force_download", False):
kwargs["force_download"] = False
# Forward cache_dir so the load reads the warmed adapter. No subfolder (that targets the
# base checkpoint; adapters live at the root).
peft_load_kwargs = {}
if kwargs.get("cache_dir") is not None:
peft_load_kwargs["cache_dir"] = kwargs["cache_dir"]
model = PeftModel.from_pretrained(
model,
old_model_name,
token = token,
revision = revision,
local_files_only = local_files_only,
is_trainable = True,
trust_remote_code = trust_remote_code,
**peft_load_kwargs,
)
# Patch it as well!
model = dispatch_model.patch_peft_model(model, use_gradient_checkpointing)
# Re-evaluate grouped MoE now the adapter is attached: an expert-LoRA block falls back
# to the original loop, an attention-only adapter keeps the grouped path. Guarded.
try:
from unsloth_zoo.temporary_patches.moe_grouped_modulelist import (
auto_enable_grouped_moe,
)
auto_enable_grouped_moe(model)
except Exception:
pass # optional speedup; never block model loading
# Patch Tiled MLP
# to turn on set UNSLOTH_TILED_MLP to "arctic", "target", or "target:{GB}""
patch_tiled_mlp_choice = os.environ.get(
"UNSLOTH_TILED_MLP", "arctic" if unsloth_tiled_mlp else "0"
)
if patch_tiled_mlp_choice != "0" or unsloth_tiled_mlp:
patch_tiled_mlp(model, patch_options_str = patch_tiled_mlp_choice)
model = _fix_rope_inv_freq(model)
model = _exclude_rope_inv_freq_from_ddp(model)
return model, tokenizer
from ..kernels import (
patch_loss_functions,
post_patch_loss_function,
)
from .vision import FastBaseModel
from .diffusion import FastDiffusionModel, is_diffusion_model_type
from transformers import (
AutoModelForCausalLM,
)
try:
from transformers import AutoModelForImageTextToText
AutoModelForVision2Seq = AutoModelForImageTextToText
except:
from transformers import AutoModelForVision2Seq
class FastModel(FastBaseModel):
@staticmethod
def _prepare_for_qat(model, qat_scheme):
model = _prepare_model_for_qat(model, qat_scheme)
return model
@staticmethod
def get_peft_model(model, *args, **kwargs):
# Route text-diffusion models (slow path) to the transformers-only PEFT helper.
if getattr(model, "_unsloth_slow_diffusion", False):
return FastDiffusionModel.get_peft_model(model, *args, **kwargs)
return FastBaseModel.get_peft_model(model, *args, **kwargs)
@staticmethod
def for_inference(model):
if getattr(model, "_unsloth_slow_diffusion", False):
return FastDiffusionModel.for_inference(model)
return FastBaseModel.for_inference(model)
@staticmethod
def for_training(model, use_gradient_checkpointing = True):
if getattr(model, "_unsloth_slow_diffusion", False):
return FastDiffusionModel.for_training(model, use_gradient_checkpointing)
return FastBaseModel.for_training(model, use_gradient_checkpointing)
@staticmethod
@_offline_aware_load
def from_pretrained(
model_name = "unsloth/Llama-3.2-11B-Vision-Instruct-bnb-4bit",
max_seq_length = 2048,
dtype = None,
load_in_4bit = True, # 4bit QLoRA
load_in_8bit = False, # 8bit LoRA
load_in_16bit = False, # 16bit LoRA
full_finetuning = False,
token = None,
device_map = "sequential",
rope_scaling = None, # [TODO] No effect
fix_tokenizer = True, # [TODO] No effect
trust_remote_code = False,
use_gradient_checkpointing = "unsloth",
resize_model_vocab = None, # [TODO] No effect
revision = None,
return_logits = False, # Return logits
fullgraph = True, # No graph breaks
use_exact_model_name = False,
auto_model = None,
whisper_language = None,
whisper_task = None,
unsloth_force_compile = False,
offload_embedding = False,
float32_mixed_precision = None, # Forces float32 mixed precision
# Add the missing vLLM/inference parameters
fast_inference = False, # uses vLLM
gpu_memory_utilization = 0.5,
float8_kv_cache = False,
random_state = 3407,
max_lora_rank = 64,
disable_log_stats = True,
qat_scheme = None,
load_in_fp8 = False, # fp8 LoRA (True, False, 'block')
unsloth_tiled_mlp = False,
target_parameters = None, # For MoE expert parameters
text_only = False, # Skip vision/audio towers and load only the text decoder
*args,
**kwargs,
):
user_config = kwargs.pop("config", None)
# Respect user-provided quantization_config (e.g. BitsAndBytesConfig)
quantization_config = kwargs.get("quantization_config", None)
if quantization_config is not None:
if isinstance(quantization_config, dict):
q_load_in_4bit = quantization_config.get("load_in_4bit", False)
q_load_in_8bit = quantization_config.get("load_in_8bit", False)
else:
q_load_in_4bit = getattr(quantization_config, "load_in_4bit", False)
q_load_in_8bit = getattr(quantization_config, "load_in_8bit", False)
if q_load_in_4bit:
load_in_4bit = True
load_in_8bit = False
if q_load_in_8bit:
load_in_8bit = True
load_in_4bit = False
# Login to allow private models
token = hf_login(token)
if whisper_language is not None:
assert type(whisper_language) is str
if whisper_task is not None:
assert type(whisper_task) is str
# Align dtype with bnb_4bit_compute_dtype if provided and dtype is unset.
if dtype is None and quantization_config is not None:
bnb_compute_dtype = None
if isinstance(quantization_config, dict):
if quantization_config.get("load_in_4bit", False):
bnb_compute_dtype = quantization_config.get("bnb_4bit_compute_dtype", None)
else:
if getattr(quantization_config, "load_in_4bit", False):
bnb_compute_dtype = getattr(quantization_config, "bnb_4bit_compute_dtype", None)
if isinstance(bnb_compute_dtype, str):
bnb_compute_dtype = getattr(torch, bnb_compute_dtype, None)
if isinstance(bnb_compute_dtype, torch.dtype):
dtype = bnb_compute_dtype
SUPPORTS_BFLOAT16 = is_bfloat16_supported()
if dtype is None:
dtype = torch.float16 if not SUPPORTS_BFLOAT16 else torch.bfloat16
elif dtype == torch.bfloat16 and not SUPPORTS_BFLOAT16:
logger.warning_once("Device does not support bfloat16. Will change to float16.")
dtype = torch.float16
assert dtype in (torch.float16, torch.bfloat16, torch.float32)
assert load_in_fp8 in (True, False, "block")
patch_compiled_autograd()
# Same best-effort wrapper as the FastLanguageModel path: unsloth_zoo's
# patch imports bitsandbytes unconditionally, so on a host without it this
# raised before the capability fallback below could take the 16bit path.
try:
patch_compiling_bitsandbytes()
except Exception as e:
print(f"Unsloth: Could not patch bitsandbytes for torch.compile - {e}")
if full_finetuning and (load_in_4bit or load_in_8bit):
print(
"Unsloth: You selected full finetuning support, but 4bit / 8bit is enabled - disabling LoRA / QLoRA."
)
load_in_4bit = False
load_in_8bit = False
load_in_fp8 = False
load_in_16bit = False
# bitsandbytes unusable (absent, or unstable as on some AMD stacks). This is
# a capability check, so it is not gated on use_exact_model_name: that only
# suppresses repo-name remapping and cannot make bitsandbytes available.
if not ALLOW_BITSANDBYTES:
# A user-supplied config sets load_in_4bit/8bit above and is forwarded
# in kwargs, so clearing the flags alone still rebuilds the bnb
# quantizer downstream. Only drop it when it asks for bnb: a GPTQ /
# AWQ / fp8 / torchao config must pass through untouched.
_quant_cfg = kwargs.get("quantization_config", None)
if isinstance(_quant_cfg, dict):
_wants_bnb = bool(
_quant_cfg.get("load_in_4bit", False) or _quant_cfg.get("load_in_8bit", False)
)
elif _quant_cfg is not None:
_wants_bnb = bool(
getattr(_quant_cfg, "load_in_4bit", False)
or getattr(_quant_cfg, "load_in_8bit", False)
)
else:
_wants_bnb = False
if (
load_in_4bit
or load_in_8bit
or _wants_bnb
or model_name.lower().endswith("-bnb-4bit")
):
print(
"Unsloth: `bitsandbytes` is unavailable here - disabling 4bit/8bit. "
"16bit LoRA and full finetuning still work."
)
# 8bit is bitsandbytes too: leaving either set sends the request on to
# Transformers, which builds the bnb quantizer and fails there.
load_in_4bit = False
load_in_8bit = False
if _wants_bnb:
kwargs.pop("quantization_config", None)
if (
int(load_in_4bit) + int(load_in_8bit) + int(load_in_16bit) + int(load_in_fp8 != False)
>= 2
):
raise RuntimeError(
"Unsloth: Can only load in 4bit or 8bit or 16bit, not a combination!\n"
"Also, we by default set `load_in_4bit = True`.\n"
"If you want 8bit finetuning, set both `load_in_4bit = False` and `load_in_8bit = True`\n"
"If you want 16bit LoRA finetuning, set `load_in_16bit = True`"
)
if qat_scheme is not None and not full_finetuning:
raise ValueError(
"Specifying `qat_scheme` in `FastLanguageModel.from_pretrained(...)` is only "
"compatible with `full_finetuning=True`. If you wish to use QAT with LoRA, "
"please pass in `qat_scheme` in `FastLanguageModel.get_peft_model(...)` instead."
)
if qat_scheme == "phone-deployment":
qat_scheme = "int8-int4"
# Distributed-safe device placement for quantized models.
# In multi-GPU (torchrun), each rank must load the model on its own device
# to avoid Accelerate device relocation errors with quantized weights.
is_quantized = load_in_4bit or load_in_8bit or load_in_fp8
if is_quantized and isinstance(device_map, str):
distributed_device_map, is_dist = prepare_device_map()
if is_dist:
device_map = distributed_device_map
if fast_inference:
if importlib.util.find_spec("vllm") is None:
raise ImportError(
"Unsloth: Please install vLLM before enabling `fast_inference`!\n"
"You can do this in a terminal via `pip install vllm`"
)
if DEVICE_TYPE_TORCH == "cuda":
for i in range(DEVICE_COUNT):
# [TODO] DGX Spark vLLM breaks
if "NVIDIA GB10" in str(torch.cuda.get_device_name(i)).upper():
print(
"Unsloth: DGX Spark detected - `fast_inference=True` is currently broken as of January 2026.\n"
"Defaulting to native Unsloth inference."
)
fast_inference = False
break
# Find FP8, BnB 4bit, other mapped names
old_model_name = model_name
fp8_mode = None
if not use_exact_model_name:
new_model_name = get_model_name(
model_name, load_in_4bit = load_in_4bit, load_in_fp8 = load_in_fp8
)
if new_model_name is None and load_in_fp8 != False:
fp8_mode = _get_fp8_mode_and_check_settings(
load_in_fp8,
fast_inference,
full_finetuning,
load_in_4bit,
load_in_8bit,
load_in_16bit,
)
model_name = _offline_quantize_to_fp8(model_name, fp8_mode, text_only = text_only)
else:
assert new_model_name is not None
model_name = new_model_name
# If mapper resolved to a pre-quantized FP8 model, disable
# on-the-fly quantization to avoid double quantization
if load_in_fp8 != False and new_model_name != old_model_name:
load_in_fp8 = False
# Check if pre-quantized models are allowed
# AMD Instinct GPUs need blocksize = 128 on bitsandbytes < 0.49.2 (our pre-quants use blocksize = 64)
if not ALLOW_PREQUANTIZED_MODELS and model_name.lower().endswith(
("-unsloth-bnb-4bit", "-bnb-4bit")
):
model_name = _strip_unsloth_bnb_4bit_suffix(model_name)
# '-bf16' hub repos load bf16; a local dir keeps the requested quant unless 16bit is set
if model_name.lower().endswith("-bf16") and (
load_in_16bit or not os.path.isdir(os.path.expanduser(model_name))
):
load_in_4bit = False
load_in_8bit = False
load_in_fp8 = False
load_in_16bit = True
# Check modelscope
if USE_MODELSCOPE and not os.path.exists(model_name):
from modelscope import snapshot_download
model_name = snapshot_download(model_name)
# First check if it's a normal model via AutoConfig
from huggingface_hub.utils import (
disable_progress_bars,
enable_progress_bars,
are_progress_bars_disabled,
)
was_disabled = are_progress_bars_disabled()
disable_progress_bars()
autoconfig_error = None
peft_error = None
autoconfig_exc = None
peft_exc = None
model_config = None
peft_config = None
# @_offline_aware_load already forced offline when needed; nested calls inherit it.
local_files_only = kwargs.get("local_files_only", False)
# Text-diffusion slow-path dispatch, factored so both the normal route (below) and the
# legacy-config fallback (in the AutoConfig except handler) share one call site.
def _dispatch_diffusion():
return FastDiffusionModel.from_pretrained(
model_name = model_name,
max_seq_length = max_seq_length,
dtype = dtype,
load_in_4bit = load_in_4bit,
load_in_8bit = load_in_8bit,
load_in_16bit = load_in_16bit,
full_finetuning = full_finetuning,
token = token,
device_map = device_map,
trust_remote_code = trust_remote_code,
revision = revision,
**kwargs,
)
try:
model_config = user_config
if model_config is None:
model_config = AutoConfig.from_pretrained(
model_name,
token = token,
revision = revision,
trust_remote_code = trust_remote_code,
local_files_only = local_files_only,
)
is_model = True
except ImportError:
raise
except Exception as error:
autoconfig_error = str(error)
autoconfig_exc = error
# Legacy text-diffusion configs use model_type "diffusion_gemma", which current
# transformers does not register by name (it ships "diffusion_gemma4"). AutoConfig
# raises before we can dispatch; route straight to the diffusion slow path, whose
# loader aliases the legacy type to the gemma4 classes.
if "diffusion_gemma" in autoconfig_error and is_diffusion_model_type("diffusion_gemma"):
return _dispatch_diffusion()
if "architecture" in autoconfig_error:
if "qwen3_5" in autoconfig_error:
raise ImportError(
f"Unsloth: Your transformers version of {transformers_version} does not support Qwen3.5.\n"
f"The minimum required version is 5.2.0.\n"
f'Try `pip install --upgrade "transformers>=5.2.0"`\n'
f"to obtain the latest transformers build, then restart this session."
)
raise ValueError(
f"`{model_name}` is not supported yet in `transformers=={transformers_version}`.\n"
f"Please update transformers via `pip install --upgrade transformers` and try again."
)
is_model = False
try:
peft_config = PeftConfig.from_pretrained(
model_name,
token = token,
revision = revision,
trust_remote_code = trust_remote_code,
local_files_only = local_files_only,
)
is_peft = True
except ImportError:
raise
except Exception as error:
peft_error = str(error)
peft_exc = error
if "architecture" in peft_error:
raise ValueError(
f"`{model_name}` is not supported yet in `transformers=={transformers_version}`.\n"
f"Please update transformers via `pip install --upgrade transformers` and try again."
)
is_peft = False
# Old transformers versions check
both_exist = (is_model and is_peft) and not SUPPORTS_LLAMA32
# Error out if both LoRA and normal model config exists.
if both_exist:
raise RuntimeError(
"Unsloth: Your repo has a LoRA adapter and a base model.\n"
"You have 2 files `config.json` and `adapter_config.json`.\n"
"We must only allow one config file.\n"
"Please separate the LoRA and base models to 2 repos."
)
if not is_model and not is_peft:
error = autoconfig_error if autoconfig_error is not None else peft_error
# Old transformers version
if "rope_scaling" in error.lower() and not SUPPORTS_LLAMA31:
raise ImportError(
f"Unsloth: Your transformers version of {transformers_version} does not support new RoPE scaling methods.\n"
f"This includes Llama 3.1. The minimum required version is 4.43.2\n"
f'Try `pip install --upgrade "transformers>=4.43.2"`\n'
f"to obtain the latest transformers build, then restart this session."
)
# Create a combined error message showing both failures
combined_error = (
"Unsloth: Failed to load model. Both AutoConfig and PeftConfig loading failed.\n\n"
f"AutoConfig error: {autoconfig_error}\n\n"
f"PeftConfig error: {peft_error}\n\n"
)
# Chain an offline-related cause if either probe had one, so @_offline_aware_load
# still retries from cache (e.g. adapter repo: permanent AutoConfig 404 + transient PeftConfig).
_cause = next(
(
e
for e in (autoconfig_exc, peft_exc)
if e is not None and _is_offline_related_error(e)
),
autoconfig_exc or peft_exc,
)
raise RuntimeError(combined_error) from _cause
model_types = get_transformers_model_type(
peft_config if peft_config is not None else model_config,
trust_remote_code = trust_remote_code,
)
model_types_all = ",".join(model_types) + ","
_maybe_advise_fla_install(model_types)
# ---- Text-diffusion models (e.g. DiffusionGemma) take a transformers-only slow path. ----
# These use a custom block-diffusion `generate` and a novel backbone, so we skip Unsloth's
# autoregressive kernel/compile patching and load the unmodified HF model (bit-identical to
# naive transformers), keeping only 4bit/8bit + PEFT LoRA conveniences.
if is_diffusion_model_type(model_types):
return _dispatch_diffusion()
# Save model types and loading method
lowered_model_name = model_name.lower()
# Build UNSLOTH_MODEL_NAME fresh from THIS load's model types + flags; do not prepend the
# inherited os.environ value (a stale "_load_in_4bit_" from an earlier load, e.g. across a
# save->reload subprocess, would push gpt-oss onto the BnB router patch when later loading
# a 16bit checkpoint -> "weights not initialized"). Only the type tokens and the load flags
# below are consumed downstream; the raw model name/path is excluded so a path containing a
# flag sentinel cannot be misread.
#
# Encode the EFFECTIVE bnb state: a non-bnb checkpoint (MXFP4/gptq/awq) has load_in_4bit
# disabled later by check_and_disable, so recording the requested flag here would route a
# native MXFP4 gpt-oss onto the BnB router patch. This is only an EARLY best-effort (an
# adapter-only PEFT repo has model_config=None here, and the base may be remapped); the
# authoritative correction is sync_unsloth_model_name_bnb_flags(...) after check_and_disable.
try:
from unsloth_zoo.utils import get_quant_type
_bnb_compatible_quant = get_quant_type(model_config) in (None, "bitsandbytes")
except Exception:
_bnb_compatible_quant = True
string = model_types_all
if load_in_4bit and _bnb_compatible_quant:
string += "_load_in_4bit_"
if load_in_8bit and _bnb_compatible_quant:
string += "_load_in_8bit_"
if load_in_16bit:
string += "_load_in_16bit_"
if load_in_fp8:
string += "load_in_fp8"
os.environ["UNSLOTH_MODEL_NAME"] = string
# Check versions
LATEST = "\nPlease use transformers via `pip install --no-deps git+https://github.com/huggingface/transformers.git`"
NIGHTLY = (
'\nPlease use nightly transformers via pip install --upgrade "transformers>=4.49.0"`'
)
# Pixtral
if "pixtral" in model_types_all and transformers_version < Version("4.49.0"):
raise RuntimeError("Unsloth: Pixtral only works on transformers >= 4.49.0." + LATEST)
# Qwen 2.5
elif "qwen2_5" in model_types_all and transformers_version < Version("4.49.0"):
raise RuntimeError("Unsloth: Qwen 2.5 only works on transformers >= 4.49.0." + LATEST)
# Gemma 4 must be before Gemma 3N and Gemma 3
elif "gemma4" in model_types_all:
if not SUPPORTS_GEMMA4:
raise RuntimeError("Unsloth: Gemma 4 requires transformers >= 5.5.0" + LATEST)
os.environ["UNSLOTH_DISABLE_STATIC_GENERATION"] = "1"
os.environ["UNSLOTH_HIGH_PRECISION_LAYERNORM"] = "1"
# Gemma 3N must be before Gemma 3
elif "gemma3n" in model_types_all:
if transformers_version < Version("4.53.0"):
raise RuntimeError(
"Unsloth: Gemma 3N only works on transformers >= 4.53.0" + LATEST
)
os.environ["UNSLOTH_DISABLE_STATIC_GENERATION"] = "1"
os.environ["UNSLOTH_FORCE_CUSTOM_DTYPE"] = (
"float16;torch.float16;torch.float16;"
"if name.endswith('norm'): "
"module._pre_set_compute_dtype = torch.float32\n"
";"
"from unsloth_zoo.temporary_patches.gemma3n import patch_Gemma3nConv_Embed_forwards; patch_Gemma3nConv_Embed_forwards()"
)
# Set norms to float32 since anyways they get upcasted to float32
# common in both gemma-3 and gemma-3n
os.environ["UNSLOTH_HIGH_PRECISION_LAYERNORM"] = "1"
# Gemma 3
elif "gemma3" in model_types_all:
if transformers_version < Version("4.50.0.dev0"):
raise RuntimeError(
"Unsloth: Gemma 3 only works on transformers >= 4.50.0." + NIGHTLY
)
# Set norms to float32 since anyways they get upcasted to float32
# common in both gemma-3 and gemma-3n
os.environ["UNSLOTH_HIGH_PRECISION_LAYERNORM"] = "1"
# ROCm/HIP: Gemma3 compiled forward produces NaN on RDNA GPUs
# (gfx1100, gfx1101, gfx1102, gfx1150, gfx1151, etc.).
# Disable torch.compile for model forward; loss compilation is fine.
# See https://github.com/unslothai/unsloth/issues/3385
from unsloth.kernels.utils import is_rdna
if is_rdna():
os.environ["UNSLOTH_COMPILE_DISABLE"] = "partial"
# Cohere
elif "cohere2" in model_types_all and transformers_version < Version("4.50.0.dev0"):
raise RuntimeError(
"Unsloth: Cohere's Command model only works on transformers >= 4.50.0." + NIGHTLY
)
# Sesame
elif "csm" in model_types_all:
os.environ["UNSLOTH_COMPILE_DISABLE"] = "partial" # Inference is too slow
os.environ["UNSLOTH_DISABLE_STATIC_GENERATION"] = "1" # Sesame fails
os.environ["UNSLOTH_FORCE_CUSTOM_DTYPE"] = (
"all;torch.float32;torch.float16;"
"if name.endswith(('_proj', 'fc1', 'fc2', 'codebook', 'head')): module.to(torch.float16)"
";"
)
# Granite 4
elif "granitemoehybrid" in model_types_all:
# Granite-4 rms norms are stored as 16 bit, but we upcast
os.environ["UNSLOTH_HIGH_PRECISION_LAYERNORM"] = "1"
os.environ["UNSLOTH_DISABLE_STATIC_GENERATION"] = "1"
# OLMo 2
elif "olmo2" in model_types_all and transformers_version < Version("4.50.0.dev0"):
raise RuntimeError("Unsloth: OLMo-2 only works on transformers >= 4.50.0." + NIGHTLY)
# OLMo 3
elif "olmo3" in model_types_all and transformers_version < Version("4.57.0.dev0"):
raise RuntimeError("Unsloth: OLMo-3 only works on transformers >= 4.57.0." + LATEST)
elif "falcon_h1" in model_types_all:
# Falcon must use float32 Triton ie TRITON_F32_DEFAULT = 'ieee'
# since Mamba kernels error out on using lower precision
os.environ["UNSLOTH_FORCE_CUSTOM_DTYPE"] = (
"float16;torch.float32;torch.float16;"
"if name.endswith(('q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj', 'head')): module.to(torch.float16)"
";"
"os.environ['TRITON_F32_DEFAULT'] = 'ieee'"
)
elif "nemotron_h" in model_types_all:
# NemotronH (hybrid Mamba-2 + Transformer) uses same Mamba kernels as Falcon-H1
# Mamba kernels need float32 Triton precision
os.environ["UNSLOTH_FORCE_CUSTOM_DTYPE"] = (
"float16;torch.float32;torch.float16;"
"if name.endswith(('q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj', 'head')): module.to(torch.float16)"
";"
"os.environ['TRITON_F32_DEFAULT'] = 'ieee'"
)
elif "gpt_oss" in model_types_all:
os.environ["UNSLOTH_DISABLE_STATIC_GENERATION"] = "1"
# Use the EFFECTIVE bnb state, not the raw flag: a native MXFP4 checkpoint loaded
# with the default load_in_4bit=True (e.g. openai/gpt-oss-20b by exact name) has
# bnb disabled later by check_and_disable, so the raw flag would wrongly pick the
# BnB dtype path. Mirrors the _load_in_4bit_ token gate above.
if not (load_in_4bit and _bnb_compatible_quant):
# Only upcast MoE biases for MXFP4, not BnB
# Set norms to float32 since anyways they get upcasted to float32
os.environ["UNSLOTH_FORCE_CUSTOM_DTYPE"] = (
"all;None;None;"
"x = 'gate_up_proj_bias'\n"
"if hasattr(module, x): "
"setattr(module, x, torch.nn.Parameter(getattr(module, x).to(torch.float32)) if isinstance(getattr(module, x), torch.nn.Parameter) else getattr(module, x).to(torch.float32))\n"
""
"x = 'down_proj_bias'\n"
"if hasattr(module, x): "
"setattr(module, x, torch.nn.Parameter(getattr(module, x).to(torch.float32)) if isinstance(getattr(module, x), torch.nn.Parameter) else getattr(module, x).to(torch.float32))\n"
""
";"
)
else:
# Set down projection compute dtype to be float32 for float16 machines
# Set norms to float32 since anyways they get upcasted to float32
os.environ["UNSLOTH_FORCE_CUSTOM_DTYPE"] = (
"torch.float16;torch.bfloat16;torch.float16;"
"if ('down_projs' in name) and hasattr(module, 'weight') and "
"torch.amax(dequantize_module_weight(module)) >= 0:"
"module._pre_set_compute_dtype = torch.float32\n"
""
"if ('mlp.router' in name) and hasattr(module, 'weight'):"
"module._pre_set_compute_dtype = torch.float32\n"
";"
)
# Set norms to float32 since anyways they get upcasted to float32
os.environ["UNSLOTH_HIGH_PRECISION_LAYERNORM"] = "1"
else:
for check_model_name in DISABLE_COMPILE_MODEL_NAMES:
if check_model_name in lowered_model_name:
os.environ["UNSLOTH_COMPILE_DISABLE"] = "partial"
os.environ["UNSLOTH_DISABLE_STATIC_GENERATION"] = "1"
if transformers_version < Version("4.50.0.dev0"):
raise RuntimeError(
f"Unsloth: {check_model_name} only works on transformers >= 4.50.0."
+ NIGHTLY
)
break
if auto_model is not None:
# All other models need to disable static cache
os.environ["UNSLOTH_DISABLE_STATIC_GENERATION"] = "1"
# New transformers need to check manually.
if SUPPORTS_LLAMA32 and is_model and is_peft:
# Check if folder exists locally
if os.path.isdir(model_name):
exist_adapter_config = os.path.exists(
os.path.join(model_name, "adapter_config.json")
)
exist_config = os.path.exists(os.path.join(model_name, "config.json"))
both_exist = exist_adapter_config and exist_config
else:
# Both AutoConfig and PeftConfig loaded successfully from this
# remote repo, so both config.json and adapter_config.json
# definitely exist -- no need for an extra HfFileSystem network call.
both_exist = True
# Get base model for PEFT:
if is_peft:
# Check base model again for PEFT
model_name = peft_config.base_model_name_or_path
if not use_exact_model_name:
model_name = get_model_name(model_name, load_in_4bit)
# Check if pre-quantized models are allowed
# AMD Instinct GPUs need blocksize = 128 on bitsandbytes < 0.49.2 (our pre-quants use blocksize = 64)
if not ALLOW_PREQUANTIZED_MODELS and model_name.lower().endswith(
("-unsloth-bnb-4bit", "-bnb-4bit")
):
model_name = _strip_unsloth_bnb_4bit_suffix(model_name)
# '-bf16' hub repos load bf16; a local dir keeps the requested quant unless 16bit is set
if model_name.lower().endswith("-bf16") and (
load_in_16bit or not os.path.isdir(os.path.expanduser(model_name))
):
load_in_4bit = False
load_in_8bit = False
load_in_fp8 = False
load_in_16bit = True
if user_config is not None:
model_config = user_config
else:
model_config = AutoConfig.from_pretrained(
model_name,
token = token,
trust_remote_code = trust_remote_code,
local_files_only = local_files_only,
)
if not was_disabled:
enable_progress_bars()
do_logging = os.environ.get("UNSLOTH_ENABLE_LOGGING", "0") == "1"
if do_logging:
redirector = contextlib.nullcontext()
else:
redirector = contextlib.redirect_stdout(open(os.devnull, "w", encoding = "utf-8"))
model_types = ["siglip"] + model_types
# Set forced float32 env flag
os.environ["UNSLOTH_FORCE_FLOAT32"] = "0"
do_forced_float32 = False
for model_type_arch in model_types:
if model_type_arch != "siglip":
break
for disable_name in FORCE_FLOAT32:
# add comma to model_types_all matching in case of exact match for end
if (
disable_name.lower() == model_type_arch.lower().replace("-", "").replace("_", "")
or disable_name.lower() in model_types_all
) and ((dtype == torch.float16) or not SUPPORTS_BFLOAT16):
os.environ["UNSLOTH_FORCE_FLOAT32"] = "1"
dtype = torch.bfloat16 # Change to bfloat16 loading
break
# Apply gradient checkpointing with smart heuristics
use_gradient_checkpointing = apply_unsloth_gradient_checkpointing(
use_gradient_checkpointing, max_seq_length, dtype
)
with redirector:
patch_loss_functions(torch_compile = False)
model_types, supports_sdpa = unsloth_compile_transformers(
dtype = dtype,
model_name = model_name,
model_types = model_types,
token = token,
sdpa_dynamic_mask = True,
sdpa_bool_masks = True,
sdpa_gqa_replace = True,
sdpa_dynamic_compile = True,
compile_attention = True,
disable_causal_masks = True,
compile_torch_modules = True,
compile_custom_modules = True,
compile_function_calls = True,
fuse_lm_head = True,
gradient_checkpointing = True,
manual_replacements = True,
fast_lora_forwards = True,
fast_residual_stream = False,
accurate_accumulation = True,
epilogue_fusion = True,
max_autotune = False,
shape_padding = True,
cudagraphs = False,
debug = False,
fullgraph = fullgraph,
import_from_cache = False,
disable = False,
return_logits = return_logits,
trust_remote_code = trust_remote_code,
unsloth_force_compile = unsloth_force_compile,
)
# Fix SDPA issues
for model_type in DISABLE_SDPA_MODEL_NAMES:
if model_type in model_types_all:
supports_sdpa = False
# Keep the local checkpoint dir as tokenizer when self-sufficient (see
# _resolve_checkpoint_tokenizer_name). A VLM also needs local processor files, else
# we fall back to the base repo so its cached processor loads.
_ckpt_arch = getattr(model_config, "architectures", None) or []
_ckpt_is_vlm = any(x.endswith("ForConditionalGeneration") for x in _ckpt_arch) or hasattr(
model_config, "vision_config"
)
tokenizer_name = _resolve_checkpoint_tokenizer_name(
old_model_name, kwargs, require_processor = _ckpt_is_vlm
)
# Capture task intent before text_only can replace a parent VLM config
# with its nested text config.
task_config_attrs = _get_user_task_config_attrs(user_config)
for _cfg_key in ("num_labels", "id2label", "label2id", "problem_type"):
_cfg_val = kwargs.get(_cfg_key, None)
if _cfg_val is not None:
task_config_attrs[_cfg_key] = _cfg_val
_num_labels = task_config_attrs.get("num_labels", None)
for _cfg_key, _cfg_val in task_config_attrs.items():
set_task_config_attr(model_config, _cfg_key, _cfg_val)
# Check if VLM
architectures = getattr(model_config, "architectures", None)
if architectures is None:
architectures = []
is_vlm = any(x.endswith("ForConditionalGeneration") for x in architectures)
is_vlm = is_vlm or hasattr(model_config, "vision_config")
load_text_only = text_only and auto_model is None
if load_text_only:
if hasattr(model_config, "vision_config"):
text_config = _get_text_only_config(model_config, old_model_name)
# Skip the vision tower only for families with their own text decoder (Gemma 3);
# others would load random weights, so keep the full model (use FastVisionModel).
text_class = resolve_model_class(AutoModelForCausalLM, text_config)
if text_class is None or not _is_family_text_decoder(
getattr(model_config, "model_type", ""),
getattr(text_config, "model_type", ""),
):
load_text_only = False
else:
logger.warning_once(
f"Loading {old_model_name} as text-only; vision/audio towers skipped. "
"Use FastVisionModel for multimodal inputs."
)
# Remap VLM text weights (tf >=5) while model_config is still the parent. #5816
_apply_text_only_key_mapping(kwargs, model_config, text_config)
model_config = text_config
is_vlm = False
else:
is_vlm = False
# If num_labels is set, use AutoModelForSequenceClassification
for _cfg_key, _cfg_val in task_config_attrs.items():
set_task_config_attr(model_config, _cfg_key, _cfg_val)
if auto_model is None:
if _num_labels is not None:
from transformers import AutoModelForSequenceClassification
auto_model = AutoModelForSequenceClassification
elif is_vlm:
# Check if the model's auto_map supports the VLM auto class.
# Some repo-code VL models register only a generic auto class and not
# AutoModelForImageTextToText/AutoModelForVision2Seq: Nemotron-VL uses
# AutoModelForCausalLM, DeepSeek-OCR uses AutoModel. Calling the VLM auto
# class on those raises "Unrecognized configuration class ... for
# AutoModelForImageTextToText", so fall back to whatever generic class the
# repo actually registered. Match the CONCRETE class name we would pass
# (AutoModelForVision2Seq aliases to AutoModelForImageTextToText on tf>=5),
# since transformers resolves remote code by that exact name -- a config
# that only registers the legacy key must still take the generic fallback.
_auto_map = getattr(model_config, "auto_map", {}) or {}
_vlm_class_name = AutoModelForVision2Seq.__name__
_has_vlm_class = _vlm_class_name in _auto_map
if not _has_vlm_class and "AutoModelForCausalLM" in _auto_map:
auto_model = AutoModelForCausalLM
elif not _has_vlm_class and "AutoModel" in _auto_map:
from transformers import AutoModel
auto_model = AutoModel
else:
auto_model = AutoModelForVision2Seq
else:
auto_model = AutoModelForCausalLM
load_in_4bit_kwargs = load_in_4bit
load_in_8bit_kwargs = load_in_8bit
if quantization_config is not None and not fast_inference:
load_in_4bit_kwargs = False
load_in_8bit_kwargs = False
model, tokenizer = FastBaseModel.from_pretrained(
model_name = model_name,
max_seq_length = max_seq_length,
dtype = _get_dtype(dtype),
load_in_4bit = load_in_4bit_kwargs,
load_in_8bit = load_in_8bit_kwargs,
load_in_16bit = load_in_16bit,
full_finetuning = full_finetuning,
token = token,
device_map = device_map,
trust_remote_code = trust_remote_code,
revision = revision if not is_peft else None,
model_types = model_types,
tokenizer_name = tokenizer_name,
auto_model = auto_model,
use_gradient_checkpointing = use_gradient_checkpointing,
supports_sdpa = supports_sdpa,
whisper_language = whisper_language,
whisper_task = whisper_task,
auto_config = model_config,
offload_embedding = offload_embedding,
float32_mixed_precision = float32_mixed_precision,
# Pass vLLM/inference parameters
fast_inference = fast_inference,
gpu_memory_utilization = gpu_memory_utilization,
float8_kv_cache = float8_kv_cache,
random_state = random_state,
max_lora_rank = max_lora_rank,
disable_log_stats = disable_log_stats,
load_in_fp8 = load_in_fp8,
text_only = load_text_only,
*args,
**kwargs,
)
if resize_model_vocab is not None:
model.resize_token_embeddings(resize_model_vocab)
# In case the model supports tagging, add the unsloth tag.
if hasattr(model, "add_model_tags"):
model.add_model_tags(
[
"unsloth",
]
)
if hasattr(tokenizer, "add_model_tags"):
tokenizer.add_model_tags(
[
"unsloth",
]
)
if load_in_4bit:
# Fix up bitsandbytes config, but respect user-provided quantization_config
if quantization_config is None:
# `load_in_4bit` is the requested flag, not the effective one: a non-bnb
# checkpoint (MXFP4/gptq/awq) had bnb disabled by check_and_disable, so stamping a
# synthetic bnb config would corrupt its real one. Only stamp bnb/unquantized.
try:
from unsloth_zoo.utils import get_quant_type
_stamp_bnb = get_quant_type(model.config) in (None, "bitsandbytes")
except Exception:
_stamp_bnb = True
if _stamp_bnb:
compute_dtype = dtype_from_config(model.config)
quantization_config = {
# Sometimes compute_dtype is not a string!!
"bnb_4bit_compute_dtype": compute_dtype,
"bnb_4bit_quant_type": "nf4",
"bnb_4bit_use_double_quant": True,
"llm_int8_enable_fp32_cpu_offload": False,
"llm_int8_has_fp16_weight": False,
"llm_int8_skip_modules": None,
"llm_int8_threshold": 6.0,
"load_in_4bit": True,
"load_in_8bit": False,
"quant_method": "bitsandbytes",
}
model.config.update({"quantization_config": quantization_config})
else:
if hasattr(quantization_config, "to_dict"):
model.config.update({"quantization_config": quantization_config.to_dict()})
elif isinstance(quantization_config, dict):
model.config.update({"quantization_config": quantization_config})
if load_in_fp8 != False:
_tag_model_with_fp8_torchao_config(model, fp8_mode)
if is_peft:
# From https://github.com/huggingface/peft/issues/184
# Now add PEFT adapters
# Gemma4 ClippableLinear wraps nn.Linear -- PEFT can't inject LoRA
# on it directly. Monkey-patch PEFT to target the inner .linear
# child instead (same patch as vision.py training path).
# See https://github.com/huggingface/peft/issues/3129
_clippable_linear_cls = None
try:
from transformers.models.gemma4.modeling_gemma4 import (
Gemma4ClippableLinear as _clippable_linear_cls,
)
except ImportError:
pass
if _clippable_linear_cls is not None:
from peft.tuners.lora.model import LoraModel as _LoraModel
_original_car = _LoraModel._create_and_replace
def _patched_car(
self,
peft_config,
adapter_name,
target,
target_name,
parent,
current_key = None,
**kwargs,
):
if isinstance(target, _clippable_linear_cls):
return _original_car(
self,
peft_config,
adapter_name,
target.linear,
"linear",
target,
current_key = current_key,
**kwargs,
)
return _original_car(
self,
peft_config,
adapter_name,
target,
target_name,
parent,
current_key = current_key,
**kwargs,
)
_LoraModel._create_and_replace = _patched_car
# Warm the adapter repo: PeftModel downloads it in-process and can hang on Xet.
_prefetched = maybe_prefetch_hf_snapshot(
old_model_name,
token = token,
revision = revision,
cache_dir = kwargs.get("cache_dir"),
local_files_only = local_files_only,
# Adapter always loads in-process via PeftModel, so warm it even under fast_inference.
fast_inference = False,
force_download = kwargs.get("force_download", False),
# Leave use_safetensors auto (inheriting base format could skip a safetensors-only
# adapter). adapter_only restricts the warm to the adapter files + root aux.
adapter_only = True,
)
# Child did the forced download; clear the flag so the load reuses the warm cache.
if _prefetched and kwargs.get("force_download", False):
kwargs["force_download"] = False
# Forward cache_dir so the load reads the warmed adapter. No subfolder (that targets the
# base checkpoint; adapters live at the root).
peft_load_kwargs = {}
if kwargs.get("cache_dir") is not None:
peft_load_kwargs["cache_dir"] = kwargs["cache_dir"]
try:
model = PeftModel.from_pretrained(
model,
old_model_name,
token = token,
revision = revision,
local_files_only = local_files_only,
is_trainable = True,
trust_remote_code = trust_remote_code,
**peft_load_kwargs,
)
finally:
# Always restore original PEFT method, even if loading fails
if _clippable_linear_cls is not None:
_LoraModel._create_and_replace = _original_car
# Patch it as well!
model = FastBaseModel.post_patch_model(
model, use_gradient_checkpointing, trust_remote_code = trust_remote_code
)
# Re-evaluate grouped MoE now the adapter is attached: an expert-LoRA block falls back
# to the original loop, an attention-only adapter keeps the grouped path. Guarded.
try:
from unsloth_zoo.temporary_patches.moe_grouped_modulelist import (
auto_enable_grouped_moe,
)
auto_enable_grouped_moe(model)
except Exception:
pass # optional speedup; never block model loading
# Apply QAT if specified
if qat_scheme is not None:
print("Unsloth: Applying QAT to mitigate quantization degradation")
model = FastModel._prepare_for_qat(model, qat_scheme)
# Patch Tiled MLP
# to turn on set UNSLOTH_TILED_MLP to "arctic", "target", or "target:{GB}""
patch_tiled_mlp_choice = os.environ.get(
"UNSLOTH_TILED_MLP", "arctic" if unsloth_tiled_mlp else "0"
)
if patch_tiled_mlp_choice != "0" or unsloth_tiled_mlp:
patch_tiled_mlp(model, patch_options_str = patch_tiled_mlp_choice)
model = _fix_rope_inv_freq(model)
model = _exclude_rope_inv_freq_from_ddp(model)
return model, tokenizer
class FastVisionModel(FastModel):
pass
class FastTextModel(FastModel):
pass