e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
2364 lines
102 KiB
Python
2364 lines
102 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 torch
|
|
from transformers import (
|
|
BitsAndBytesConfig,
|
|
AutoProcessor,
|
|
AutoTokenizer,
|
|
AutoModelForCausalLM,
|
|
)
|
|
|
|
try:
|
|
from transformers import AutoModelForImageTextToText
|
|
AutoModelForVision2Seq = AutoModelForImageTextToText
|
|
except:
|
|
from transformers import AutoModelForVision2Seq
|
|
from ..kernels import (
|
|
post_patch_loss_function,
|
|
)
|
|
from ._utils import (
|
|
__version__,
|
|
importlib_version,
|
|
_prepare_model_for_qat,
|
|
resolve_model_class,
|
|
resolve_attention_implementation,
|
|
_get_text_only_config,
|
|
_is_family_text_decoder,
|
|
_apply_text_only_key_mapping,
|
|
_select_moe_detection_targets,
|
|
set_task_config_attr,
|
|
)
|
|
from ._utils import *
|
|
from .loader_utils import (
|
|
_exclude_rope_inv_freq_from_ddp,
|
|
_get_fp8_mode_and_check_settings,
|
|
_restore_dropped_fp8_scales,
|
|
)
|
|
from ..save import patch_saving_functions
|
|
from ..models.loader_utils import is_distributed
|
|
from unsloth_zoo.gradient_checkpointing import (
|
|
unpatch_unsloth_gradient_checkpointing,
|
|
unpatch_unsloth_smart_gradient_checkpointing,
|
|
)
|
|
import torch.utils.checkpoint as torch_checkpoint
|
|
import transformers.modeling_utils as hf_modeling_utils
|
|
from peft import LoraConfig, TaskType, get_peft_model as _get_peft_model
|
|
from peft import PeftModelForCausalLM
|
|
from transformers import set_seed as transformers_set_seed
|
|
from unsloth_zoo.peft_utils import (
|
|
get_peft_regex,
|
|
SKIP_QUANTIZATION_MODULES,
|
|
requires_grad_for_gradient_checkpointing,
|
|
)
|
|
from transformers.models.llama.modeling_llama import logger
|
|
from transformers import __version__ as transformers_version
|
|
from triton import __version__ as triton_version
|
|
from unsloth_zoo.utils import _get_dtype
|
|
from unsloth_zoo.hf_utils import (
|
|
dtype_from_config,
|
|
add_dtype_kwargs,
|
|
fix_lora_auto_mapping,
|
|
get_auto_processor,
|
|
)
|
|
from unsloth_zoo.patching_utils import patch_model_and_tokenizer
|
|
from unsloth_zoo.training_utils import prepare_model_for_training
|
|
|
|
from unsloth_zoo.utils import Version
|
|
from transformers import __version__ as transformers_version
|
|
|
|
import types
|
|
import functools
|
|
import os
|
|
import gc
|
|
import math
|
|
import warnings
|
|
from typing import Optional, Tuple, List, Union
|
|
import re, inspect, sys
|
|
import contextlib
|
|
|
|
try:
|
|
from huggingface_hub.utils import get_token
|
|
except:
|
|
# Old HF Hub versions <= 0.0.25
|
|
from huggingface_hub.utils._token import get_token
|
|
from ..device_type import (
|
|
is_hip,
|
|
get_device_type,
|
|
DEVICE_TYPE,
|
|
DEVICE_TYPE_TORCH,
|
|
DEVICE_COUNT,
|
|
ALLOW_PREQUANTIZED_MODELS,
|
|
)
|
|
|
|
__all__ = [
|
|
"FastBaseModel",
|
|
]
|
|
|
|
|
|
def _infer_device_map_from_loaded_model(model):
|
|
"""Build a compact device_map by inspecting actual parameter placements."""
|
|
device_map = {}
|
|
|
|
def _assign(module, prefix):
|
|
params = list(module.named_parameters(remove_duplicate = False))
|
|
if not params:
|
|
bufs = list(module.named_buffers())
|
|
if bufs:
|
|
device_map[prefix] = bufs[0][1].device
|
|
return
|
|
devices = {p.device for _, p in params}
|
|
if len(devices) == 1:
|
|
device_map[prefix] = next(iter(devices))
|
|
else:
|
|
for child_name, child in module.named_children():
|
|
child_prefix = f"{prefix}.{child_name}" if prefix else child_name
|
|
_assign(child, child_prefix)
|
|
for pname, param in module.named_parameters(remove_duplicate = False):
|
|
if "." not in pname:
|
|
full = f"{prefix}.{pname}" if prefix else pname
|
|
if not any(full == k or full.startswith(k + ".") for k in device_map):
|
|
device_map[full] = param.device
|
|
|
|
_assign(model, "")
|
|
if "" in device_map and len(device_map) > 1:
|
|
device_map.pop("")
|
|
return device_map
|
|
|
|
|
|
def _attach_bnb_multidevice_hooks(
|
|
model, load_in_4bit, load_in_8bit, offload_embedding, fast_inference
|
|
):
|
|
"""
|
|
Attach accelerate AlignDevicesHook on a bnb model loaded across multiple
|
|
devices (or a non-default device). No-op for single-GPU cuda:0, non-bnb,
|
|
vLLM, or already-dispatched models.
|
|
"""
|
|
if fast_inference:
|
|
return
|
|
is_bnb = (
|
|
load_in_4bit
|
|
or load_in_8bit
|
|
or getattr(model, "is_loaded_in_4bit", False)
|
|
or getattr(model, "is_loaded_in_8bit", False)
|
|
or getattr(model, "quantization_method", None) == "bitsandbytes"
|
|
)
|
|
if not is_bnb:
|
|
return
|
|
if offload_embedding:
|
|
return
|
|
if getattr(model, "hf_device_map", None) is not None:
|
|
return # already dispatched
|
|
|
|
try:
|
|
all_devs = {p.device for p in model.parameters()}
|
|
except Exception as exc:
|
|
warnings.warn(
|
|
"Unsloth: Failed to determine device placement from model parameters, "
|
|
f"so multi-GPU hooks cannot be attached. ({type(exc).__name__}: {exc})",
|
|
RuntimeWarning,
|
|
stacklevel = 2,
|
|
)
|
|
return
|
|
|
|
cuda_devs = {d for d in all_devs if d.type == "cuda"}
|
|
if not cuda_devs:
|
|
return
|
|
|
|
default_cuda = torch.device("cuda", 0)
|
|
if all_devs == {default_cuda}:
|
|
return
|
|
|
|
try:
|
|
from accelerate import dispatch_model
|
|
except ImportError:
|
|
return # accelerate not available
|
|
|
|
try:
|
|
inferred_map = _infer_device_map_from_loaded_model(model)
|
|
if not inferred_map:
|
|
return
|
|
|
|
# bnb constructors reject _is_hf_initialized; strip before dispatch.
|
|
_extra_keys = ("_is_hf_initialized",)
|
|
_stripped = []
|
|
for _, param in model.named_parameters():
|
|
for key in _extra_keys:
|
|
if key in param.__dict__:
|
|
_stripped.append((param, key, param.__dict__.pop(key)))
|
|
|
|
try:
|
|
# CUDA -> int index, non-CUDA -> type string ("cpu", "meta").
|
|
device_map_int = {
|
|
k: (v.index if v.type == "cuda" else v.type) if isinstance(v, torch.device) else v
|
|
for k, v in inferred_map.items()
|
|
}
|
|
|
|
# force_hooks=True: install hooks even for single-device maps.
|
|
main_device = device_map_int.get("")
|
|
if main_device in (None, "cpu", "disk"):
|
|
main_device = next(
|
|
(d for d in device_map_int.values() if d not in ("cpu", "disk")),
|
|
None,
|
|
)
|
|
dispatch_model(
|
|
model,
|
|
device_map = device_map_int,
|
|
main_device = main_device,
|
|
skip_keys = getattr(model, "_skip_keys_device_placement", None),
|
|
force_hooks = True,
|
|
)
|
|
desc = f"{len(inferred_map)} block(s) across {len(cuda_devs)} device(s)"
|
|
finally:
|
|
# Restore stripped keys.
|
|
for param, key, val in _stripped:
|
|
param.__dict__[key] = val
|
|
|
|
logger.info(
|
|
f"Unsloth: Attached accelerate AlignDevicesHook ({desc}) "
|
|
f"for bnb multi-GPU inference."
|
|
)
|
|
except Exception as exc:
|
|
warnings.warn(
|
|
f"Unsloth: Could not attach multi-device dispatch hooks automatically "
|
|
f"({type(exc).__name__}: {exc}). "
|
|
"Cross-device inference may fail. Consider using a single GPU or "
|
|
"calling accelerate.dispatch_model() manually.",
|
|
RuntimeWarning,
|
|
stacklevel = 2,
|
|
)
|
|
|
|
|
|
global NUM_LOGITS_TO_KEEP
|
|
NUM_LOGITS_TO_KEEP = dict()
|
|
|
|
|
|
def _unsloth_generate_accepts_kwarg(model, key):
|
|
# True if the top level accepts this generate kwarg (some models expose it on an inner forward only).
|
|
try:
|
|
model_args = set(inspect.signature(model.prepare_inputs_for_generation).parameters)
|
|
except (TypeError, ValueError, AttributeError):
|
|
model_args = set()
|
|
if "kwargs" in model_args or "model_kwargs" in model_args:
|
|
try:
|
|
model_args |= set(inspect.signature(model.forward).parameters)
|
|
except (TypeError, ValueError, AttributeError):
|
|
pass
|
|
return key in model_args
|
|
|
|
|
|
def _install_offload_embedding_hooks(embed_tokens, output_embeddings, return_device):
|
|
# Lookup runs on the weight's current device (CPU when offloaded); the output returns to the
|
|
# decoder device read live from output_embeddings (lm_head, untied here) so it tracks
|
|
# model.to() moves. A meta (disk-offloaded) or missing lm_head falls back to return_device.
|
|
if embed_tokens is None:
|
|
return False
|
|
if getattr(embed_tokens, "_unsloth_offload_hooks_installed", False):
|
|
return True
|
|
|
|
def _decoder_device():
|
|
weight = getattr(output_embeddings, "weight", None)
|
|
if weight is not None and weight.device.type != "meta":
|
|
return weight.device
|
|
return return_device
|
|
|
|
def _unsloth_offload_pre_hook(module, args):
|
|
if not args:
|
|
return args
|
|
inp = args[0]
|
|
if not hasattr(inp, "device"):
|
|
return args
|
|
weight = getattr(module, "weight", None)
|
|
target = weight.device if weight is not None else _decoder_device()
|
|
if target is None or inp.device == target:
|
|
return args
|
|
return (inp.to(target),) + tuple(args[1:])
|
|
|
|
def _unsloth_offload_post_hook(module, args, output):
|
|
target = _decoder_device()
|
|
if target is not None and hasattr(output, "device") and output.device != target:
|
|
return output.to(target)
|
|
return output
|
|
|
|
embed_tokens.register_forward_pre_hook(_unsloth_offload_pre_hook, prepend = True)
|
|
embed_tokens.register_forward_hook(_unsloth_offload_post_hook, prepend = True)
|
|
embed_tokens._unsloth_offload_hooks_installed = True
|
|
return True
|
|
|
|
|
|
def _embeddings_are_tied(input_embeddings, output_embeddings):
|
|
# Tied lm_head reuses this weight; offloading to CPU would strand the output projection.
|
|
if input_embeddings is None or output_embeddings is None:
|
|
return False
|
|
w_in = getattr(input_embeddings, "weight", None)
|
|
w_out = getattr(output_embeddings, "weight", None)
|
|
if w_in is None or w_out is None:
|
|
return False
|
|
return w_in is w_out or w_in.data_ptr() == w_out.data_ptr()
|
|
|
|
|
|
VLLM_SUPPORTED_VLM = [
|
|
"qwen2_5_vl",
|
|
"gemma3",
|
|
"mistral3",
|
|
"qwen3_vl",
|
|
"qwen3_vl_moe",
|
|
]
|
|
VLLM_NON_LORA_VLM = [
|
|
"mllama",
|
|
]
|
|
PRE_COMPILE_INFERENCE = [
|
|
"gpt_oss",
|
|
]
|
|
|
|
from transformers import GenerationConfig, CompileConfig, AutoConfig
|
|
|
|
try:
|
|
from transformers import PreTrainedConfig
|
|
PretrainedConfig = PreTrainedConfig
|
|
except:
|
|
from transformers import PretrainedConfig
|
|
|
|
HAS_TORCH_DTYPE = "torch_dtype" in PretrainedConfig.__doc__
|
|
|
|
_compile_config = CompileConfig(
|
|
fullgraph = False,
|
|
dynamic = None,
|
|
mode = "reduce-overhead",
|
|
)
|
|
_compile_config.disable = True # Must set manually
|
|
|
|
try:
|
|
torch_compiler_set_stance = torch.compiler.set_stance
|
|
except:
|
|
torch_compiler_set_stance = None
|
|
|
|
|
|
def unsloth_base_fast_generate(self, *args, **kwargs):
|
|
if len(args) != 0:
|
|
input_ids = args[0]
|
|
elif "input_ids" in kwargs:
|
|
input_ids = kwargs["input_ids"]
|
|
elif "input" in kwargs:
|
|
input_ids = kwargs["input"]
|
|
elif "input_features" in kwargs:
|
|
input_ids = kwargs["input_features"]
|
|
elif "inputs_embeds" in kwargs:
|
|
# canonical HF name for embedding inputs (e.g. multimodal generate)
|
|
input_ids = kwargs["inputs_embeds"]
|
|
elif "input_embeds" in kwargs:
|
|
input_ids = kwargs["input_embeds"]
|
|
elif "inputs" in kwargs:
|
|
input_ids = kwargs["inputs"]
|
|
else:
|
|
key = next(iter(kwargs.keys()))
|
|
if type(kwargs[key]) is not torch.Tensor:
|
|
raise TypeError("Unsloth: You need to pass in input_ids to .generate!")
|
|
input_ids = kwargs[key]
|
|
assert type(input_ids) is torch.Tensor
|
|
bsz = input_ids.shape[0]
|
|
|
|
FastBaseModel.for_inference(self)
|
|
dtype = _get_dtype(dtype_from_config(self.config))
|
|
# Handle full float32 cases as config.dtype == torch.float32!
|
|
do_bfloat16_mixed_precision = os.environ.get("UNSLOTH_BFLOAT16_MIXED_PRECISION", "0") == "1"
|
|
if do_bfloat16_mixed_precision:
|
|
dtype = torch.bfloat16
|
|
|
|
is_vlm = any(
|
|
x.endswith(("ForConditionalGeneration", "ForVisionText2Text"))
|
|
for x in self.config.architectures
|
|
)
|
|
is_vlm = is_vlm or hasattr(self.config, "vision_config")
|
|
arch = self.config.architectures[0]
|
|
|
|
# Remove token_type_ids - WRONG for Gemma 3 since bidirectional attention
|
|
if hasattr(self, "generate") and hasattr(self, "forward"):
|
|
# did not combine with below since self might not have model
|
|
keys = inspect.signature(self.forward).parameters.keys()
|
|
if "token_type_ids" not in keys:
|
|
kwargs.pop("token_type_ids", None)
|
|
# kwargs.pop("token_type_ids", None)
|
|
|
|
# Vision processors emit mm_token_type_ids that generate() rejects (Qwen3-VL); unlike
|
|
# logits_to_keep it is an incoming kwarg, so drop it when generate does not accept it.
|
|
if "mm_token_type_ids" in kwargs and not _unsloth_generate_accepts_kwarg(
|
|
self, "mm_token_type_ids"
|
|
):
|
|
kwargs.pop("mm_token_type_ids", None)
|
|
|
|
# VLMs do not allow logits_to_keep
|
|
global NUM_LOGITS_TO_KEEP
|
|
if arch not in NUM_LOGITS_TO_KEEP:
|
|
m = self
|
|
# Find which is used: num_logits_to_keep or logits_to_keep
|
|
while hasattr(m, "model"):
|
|
if hasattr(m, "forward"):
|
|
keys = inspect.signature(m.forward).parameters.keys()
|
|
if "num_logits_to_keep" in keys:
|
|
NUM_LOGITS_TO_KEEP[arch] = "num_logits_to_keep"
|
|
break
|
|
elif "logits_to_keep" in keys:
|
|
NUM_LOGITS_TO_KEEP[arch] = "logits_to_keep"
|
|
break
|
|
m = m.model
|
|
if arch not in NUM_LOGITS_TO_KEEP:
|
|
NUM_LOGITS_TO_KEEP[arch] = None
|
|
key = NUM_LOGITS_TO_KEEP[arch]
|
|
if key is not None and key not in kwargs and _unsloth_generate_accepts_kwarg(self, key):
|
|
kwargs[key] = 1
|
|
|
|
model_eos_token_id = getattr(self.config, "eos_token_id", None)
|
|
if model_eos_token_id is not None and hasattr(model_eos_token_id, "__iter__"):
|
|
model_eos_token_id = model_eos_token_id[0]
|
|
|
|
kwargs["pad_token_id"] = kwargs.pop("pad_token_id", model_eos_token_id)
|
|
|
|
# Get pixel values for VLMs
|
|
try:
|
|
kwargs["pixel_values"] = kwargs["pixel_values"].to(dtype)
|
|
except:
|
|
pass
|
|
try:
|
|
kwargs["pixel_values_videos"] = kwargs["pixel_values_videos"].to(dtype)
|
|
except:
|
|
pass
|
|
|
|
# Mixed precision autocast
|
|
if os.environ.get("UNSLOTH_FORCE_FLOAT32", "0") == "1":
|
|
autocaster = torch.autocast(device_type = DEVICE_TYPE_TORCH, dtype = torch.float16)
|
|
dtype = torch.float16
|
|
else:
|
|
autocaster = torch.autocast(device_type = DEVICE_TYPE_TORCH, dtype = dtype)
|
|
# Prepare LoRA
|
|
# state_dict = convert_lora_modules(self, dtype = dtype)
|
|
|
|
# Set compile dynamic shapes
|
|
torch._dynamo.mark_static(input_ids, 0)
|
|
torch._dynamo.mark_dynamic(input_ids, 1)
|
|
if "attention_mask" in kwargs:
|
|
torch._dynamo.mark_static(kwargs["attention_mask"], 0)
|
|
torch._dynamo.mark_dynamic(kwargs["attention_mask"], 1)
|
|
if "token_type_ids" in kwargs:
|
|
torch._dynamo.mark_static(kwargs["token_type_ids"], 0)
|
|
torch._dynamo.mark_dynamic(kwargs["token_type_ids"], 1)
|
|
|
|
# Fix generation_config
|
|
# Use hybrid if sliding window seen, otherwise try static
|
|
cache_implementation = getattr(self.config, "cache_implementation", None)
|
|
if getattr(self, "_supports_static_cache", getattr(self, "_can_compile_fullgraph", True)):
|
|
if os.environ.get("UNSLOTH_DISABLE_STATIC_GENERATION", "0") == "0":
|
|
cache_implementation = "static"
|
|
elif Version(transformers_version) < Version("4.56.0.dev0"):
|
|
cache_implementation = None
|
|
else:
|
|
# Should work in latest transformers!
|
|
cache_implementation = "static"
|
|
else:
|
|
cache_implementation = None
|
|
if cache_implementation is not None:
|
|
swa = getattr(getattr(self.config, "text_config", self.config), "sliding_window", None)
|
|
if (swa == 0 or type(swa) is not int) and (
|
|
getattr(self, "_can_compile_fullgraph", True) is True
|
|
):
|
|
cache_implementation = "static"
|
|
else:
|
|
if Version(transformers_version) < Version("4.56.0.dev0"):
|
|
cache_implementation = "hybrid"
|
|
else:
|
|
cache_implementation = "static"
|
|
# [TODO] Unsure why static fails
|
|
if do_bfloat16_mixed_precision:
|
|
cache_implementation = None
|
|
|
|
if "generation_config" in kwargs:
|
|
kwargs["generation_config"].cache_implementation = cache_implementation
|
|
if cache_implementation is not None:
|
|
kwargs["generation_config"].compile_config = _compile_config
|
|
else:
|
|
kwargs["cache_implementation"] = cache_implementation
|
|
if cache_implementation is not None:
|
|
kwargs["compile_config"] = _compile_config
|
|
|
|
# Delete cached Flex Attention masks to reset inference
|
|
for name, module in self.named_modules():
|
|
if hasattr(module, "_flex_attention_cache"):
|
|
try:
|
|
del module._flex_attention_cache
|
|
except:
|
|
pass
|
|
# Solves AttributeError: 'SlidingWindowLayer' object has no attribute 'max_batch_size'
|
|
if hasattr(module, "_cache") and "cache_utils" in str(module._cache.__class__):
|
|
try:
|
|
del module._cache
|
|
except:
|
|
pass
|
|
|
|
with torch.inference_mode(), autocaster:
|
|
output = self._old_generate(*args, **kwargs)
|
|
|
|
# Delete cached Flex Attention masks to reset inference
|
|
for name, module in self.named_modules():
|
|
if hasattr(module, "_flex_attention_cache"):
|
|
try:
|
|
del module._flex_attention_cache
|
|
except:
|
|
pass
|
|
# Solves AttributeError: 'SlidingWindowLayer' object has no attribute 'max_batch_size'
|
|
if hasattr(module, "_cache") and "cache_utils" in str(module._cache.__class__):
|
|
try:
|
|
del module._cache
|
|
except:
|
|
pass
|
|
|
|
# FastBaseModel.for_training(self)
|
|
return output
|
|
|
|
|
|
# Offline helpers live in loader_utils.py (shared canonical source).
|
|
from .loader_utils import (
|
|
_get_effective_local_files_only,
|
|
_is_offline_related_error,
|
|
_offline_aware_load,
|
|
)
|
|
|
|
|
|
def _missing_torchvision_error(error = None):
|
|
"""True if a VLM processor failed to load due to missing torchvision (#4202).
|
|
|
|
Checks availability directly first, then only the specific torchvision-required
|
|
error text (not any incidental "torchvision" substring like a model path)."""
|
|
import importlib.util
|
|
|
|
if importlib.util.find_spec("torchvision") is None:
|
|
return True
|
|
if error is not None:
|
|
error_str = str(error).lower()
|
|
return (
|
|
"requires the torchvision" in error_str or "no module named 'torchvision'" in error_str
|
|
)
|
|
return False
|
|
|
|
|
|
def _construct_vlm_processor_fallback(
|
|
tokenizer_name,
|
|
model_type,
|
|
token,
|
|
trust_remote_code,
|
|
cache_dir = None,
|
|
local_files_only = False,
|
|
):
|
|
"""Build a VLM processor manually when AutoProcessor.from_pretrained fails (some VLMs
|
|
have unresolvable tokenizer_class entries): load the image processor + tokenizer
|
|
separately and combine. Returns (processor_or_None, error_or_None) so the caller can
|
|
tell an offline failure (retry from cache) from a genuine one."""
|
|
_fb_err = None
|
|
try:
|
|
from transformers import AutoImageProcessor, PreTrainedTokenizerFast, AutoConfig
|
|
from transformers.models.auto.processing_auto import PROCESSOR_MAPPING_NAMES
|
|
import json
|
|
|
|
# Load image processor
|
|
image_processor = AutoImageProcessor.from_pretrained(
|
|
tokenizer_name,
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = cache_dir,
|
|
local_files_only = local_files_only,
|
|
)
|
|
# Load tokenizer via PreTrainedTokenizerFast (bypasses tokenizer_class check)
|
|
tok = PreTrainedTokenizerFast.from_pretrained(
|
|
tokenizer_name,
|
|
padding_side = "left",
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = cache_dir,
|
|
local_files_only = local_files_only,
|
|
)
|
|
# Read tokenizer_config.json for special tokens: prefer the local file (offline
|
|
# / local checkpoint dir), else hf_hub_download with local_files_only forwarded.
|
|
try:
|
|
import json as _json
|
|
|
|
tok_config = None
|
|
_local_cfg = os.path.join(tokenizer_name, "tokenizer_config.json")
|
|
if os.path.isdir(tokenizer_name):
|
|
# Local dir: read directly. A missing file raises a clear FileNotFoundError
|
|
# rather than letting hf_hub_download treat the path as a repo id.
|
|
if os.path.exists(_local_cfg):
|
|
with open(_local_cfg, "r", encoding = "utf-8") as f:
|
|
tok_config = _json.load(f)
|
|
else:
|
|
raise FileNotFoundError(
|
|
f"tokenizer_config.json not found in local directory: {tokenizer_name}"
|
|
)
|
|
else:
|
|
from huggingface_hub import hf_hub_download
|
|
config_path = hf_hub_download(
|
|
tokenizer_name,
|
|
"tokenizer_config.json",
|
|
token = token,
|
|
cache_dir = cache_dir,
|
|
local_files_only = local_files_only,
|
|
)
|
|
with open(config_path, "r", encoding = "utf-8") as f:
|
|
tok_config = _json.load(f)
|
|
# Set model-specific special tokens and their IDs
|
|
for key in (
|
|
"image_token",
|
|
"image_start_token",
|
|
"image_end_token",
|
|
"image_thumbnail",
|
|
"video_token",
|
|
):
|
|
if key in tok_config and not hasattr(tok, key):
|
|
setattr(tok, key, tok_config[key])
|
|
id_key = key + "_id" if not key.endswith("_id") else key
|
|
token_id = tok.convert_tokens_to_ids(tok_config[key])
|
|
if not hasattr(tok, id_key):
|
|
setattr(tok, id_key, token_id)
|
|
except Exception as _e:
|
|
_fb_err = _e # remember (non-fatal here); surfaced only if no processor is built
|
|
|
|
# Find the processor class - try model_type first, then top-level config model_type
|
|
proc_class_name = PROCESSOR_MAPPING_NAMES.get(model_type)
|
|
if proc_class_name is None:
|
|
# model_type might be a sub-model type (e.g. "lfm2" instead of "lfm2_vl").
|
|
# Try the top-level config.model_type which often has the processor mapping.
|
|
try:
|
|
config = AutoConfig.from_pretrained(
|
|
tokenizer_name,
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = cache_dir,
|
|
local_files_only = local_files_only,
|
|
)
|
|
proc_class_name = PROCESSOR_MAPPING_NAMES.get(config.model_type)
|
|
except Exception as _e:
|
|
_fb_err = _e # surface a network/cache miss so the offline retry can fire
|
|
|
|
if proc_class_name is not None:
|
|
import transformers
|
|
proc_class = getattr(transformers, proc_class_name, None)
|
|
if proc_class is not None:
|
|
processor = proc_class(image_processor = image_processor, tokenizer = tok)
|
|
# Copy chat_template from tokenizer to processor if needed
|
|
if not getattr(processor, "chat_template", None) and getattr(
|
|
tok, "chat_template", None
|
|
):
|
|
processor.chat_template = tok.chat_template
|
|
return processor, None
|
|
except Exception as _e:
|
|
_fb_err = _e
|
|
return None, _fb_err
|
|
|
|
|
|
def _get_total_transformer_layers(model):
|
|
"""Best-effort total transformer block count across HF model shapes.
|
|
Returns None if not determinable; caller should skip the conversion."""
|
|
cfg = getattr(model, "config", None)
|
|
if cfg is None:
|
|
return None
|
|
for name in (
|
|
"num_hidden_layers",
|
|
"n_layer",
|
|
"n_layers",
|
|
"num_layers",
|
|
):
|
|
v = getattr(cfg, name, None)
|
|
if isinstance(v, int) and v > 0:
|
|
return v
|
|
text_cfg = getattr(cfg, "text_config", None)
|
|
if text_cfg is not None:
|
|
for name in (
|
|
"num_hidden_layers",
|
|
"n_layer",
|
|
"n_layers",
|
|
"num_layers",
|
|
):
|
|
v = getattr(text_cfg, name, None)
|
|
if isinstance(v, int) and v > 0:
|
|
return v
|
|
return None
|
|
|
|
|
|
class FastBaseModel:
|
|
@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,
|
|
load_in_8bit = False,
|
|
load_in_16bit = False,
|
|
full_finetuning = False,
|
|
token = None,
|
|
device_map = "sequential",
|
|
trust_remote_code = False,
|
|
model_types = None,
|
|
tokenizer_name = None,
|
|
auto_model = AutoModelForVision2Seq,
|
|
use_gradient_checkpointing = "unsloth",
|
|
supports_sdpa = True,
|
|
whisper_language = None,
|
|
whisper_task = None,
|
|
auto_config = None,
|
|
offload_embedding = False,
|
|
float32_mixed_precision = None, # Forces float32 mixed precision
|
|
# vLLM parameters
|
|
fast_inference = False,
|
|
gpu_memory_utilization = 0.5,
|
|
float8_kv_cache = False,
|
|
random_state = 3407,
|
|
max_lora_rank = 64,
|
|
disable_log_stats = False,
|
|
unsloth_vllm_standby = False,
|
|
load_in_fp8 = False, # fp8 LoRA (True, False, 'block')
|
|
text_only = False,
|
|
**kwargs,
|
|
):
|
|
user_config = kwargs.pop("config", None)
|
|
if auto_config is None and user_config is not None:
|
|
auto_config = user_config
|
|
|
|
# Offline snapshot for the loads below; not popped, so the weight load still
|
|
# reads local_files_only from **kwargs. See _get_effective_local_files_only.
|
|
local_files_only = _get_effective_local_files_only(kwargs)
|
|
|
|
if unsloth_vllm_standby and os.environ.get("UNSLOTH_VLLM_STANDBY", "0") != "1":
|
|
raise RuntimeError(
|
|
"Unsloth: UNSLOTH_VLLM_STANDBY is True, but UNSLOTH_VLLM_STANDBY is not set to 1!"
|
|
)
|
|
|
|
if model_types is None:
|
|
raise RuntimeError(
|
|
"Unsloth: Please use FastModel or FastVisionModel and not use FastBaseModel directly!"
|
|
)
|
|
if os.environ.get("UNSLOTH_MODEL_NAME", "") == "":
|
|
os.environ["UNSLOTH_MODEL_NAME"] = model_name.lower()
|
|
|
|
# Resolve text-only before the is_vlm / vLLM checks so is_vlm stays consistent;
|
|
# skip the vision tower only for families with their own text decoder (Gemma 3). #5816
|
|
if text_only and auto_config is None:
|
|
auto_config = AutoConfig.from_pretrained(
|
|
model_name,
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
local_files_only = local_files_only,
|
|
)
|
|
if text_only and hasattr(auto_config, "vision_config"):
|
|
parent_config = auto_config
|
|
text_config = _get_text_only_config(parent_config, model_name)
|
|
text_class = resolve_model_class(AutoModelForCausalLM, text_config)
|
|
if text_class is not None and _is_family_text_decoder(
|
|
getattr(parent_config, "model_type", ""),
|
|
getattr(text_config, "model_type", ""),
|
|
):
|
|
auto_config = text_config
|
|
auto_model = AutoModelForCausalLM
|
|
_apply_text_only_key_mapping(kwargs, parent_config, text_config)
|
|
elif text_only and auto_model in [
|
|
AutoModelForVision2Seq,
|
|
AutoModelForImageTextToText,
|
|
]:
|
|
# Pure text model requested text-only with a VLM auto class.
|
|
auto_model = AutoModelForCausalLM
|
|
is_vlm = auto_model in [AutoModelForVision2Seq, AutoModelForImageTextToText]
|
|
# A repo-code VLM may register only AutoModel / AutoModelForCausalLM (e.g.
|
|
# DeepSeek-OCR, Nemotron-VL), so auto_model is not a VLM class even though the
|
|
# config is a vision model. Keep is_vlm (auto-class derived) for processor
|
|
# selection below -- these repos ship no AutoProcessor -- but treat the model as a
|
|
# VLM on the vLLM path so a vision_config model is never silently loaded/converted
|
|
# as text-only. text_only resolves the tower away first, so honour that here.
|
|
is_vlm_config = is_vlm or (not text_only and hasattr(auto_config, "vision_config"))
|
|
is_whisper = whisper_language is not None and whisper_task is not None
|
|
auto_processor = AutoProcessor if (is_vlm or is_whisper) else AutoTokenizer
|
|
|
|
model_type_arch = model_types[0]
|
|
if model_type_arch == "siglip":
|
|
for model_type_arch in model_types:
|
|
if model_type_arch != "siglip":
|
|
break
|
|
|
|
vllm_enable_lora = True
|
|
|
|
if is_vlm_config and fast_inference:
|
|
if not any(arch in VLLM_SUPPORTED_VLM for arch in model_types):
|
|
raise RuntimeError(
|
|
f"Unsloth: Fast inference is only supported for Language models and Qwen2.5-VL, Gemma3 among vision models. "
|
|
f"Found architectures: {', '.join(model_types)}!"
|
|
)
|
|
|
|
if any(arch in VLLM_NON_LORA_VLM for arch in model_types):
|
|
# mllama is still only in vllm v0 https://arc.net/l/quote/llwkfgmu
|
|
# https://docs.vllm.ai/en/stable/models/supported_models.html#text-generation_1
|
|
# vLLM V0 does not support LoRA on multi modal models.
|
|
# TODO: Update this once vLLM V1 supports Llama 3.2 aka mllama
|
|
vllm_enable_lora = False
|
|
|
|
os.environ["UNSLOTH_USE_NEW_MODEL"] = "1"
|
|
if trust_remote_code:
|
|
print(
|
|
"Unsloth: WARNING `trust_remote_code` is True.\n"
|
|
"Are you certain you want to do remote code execution?"
|
|
)
|
|
token = hf_login(token)
|
|
SUPPORTS_BFLOAT16 = is_bfloat16_supported()
|
|
|
|
if DEVICE_TYPE == "cuda":
|
|
gpu_stats = torch.cuda.get_device_properties(0)
|
|
gpu_stats_name = (
|
|
gpu_stats.name + ". " if gpu_stats.name != "" else "NVIDIA GPU Device. "
|
|
)
|
|
gpu_version = torch.version.cuda
|
|
gpu_stats_snippet = (
|
|
f"CUDA: {gpu_stats.major}.{gpu_stats.minor}. CUDA Toolkit: {gpu_version}."
|
|
)
|
|
try:
|
|
vllm_version = f" vLLM: {importlib_version('vllm')}."
|
|
except:
|
|
vllm_version = ""
|
|
elif DEVICE_TYPE == "hip":
|
|
gpu_stats = torch.cuda.get_device_properties(0)
|
|
gpu_stats_name = resolve_hip_gpu_stats_name(gpu_stats)
|
|
gpu_version = torch.version.hip
|
|
gpu_stats_snippet = f"ROCm Toolkit: {gpu_version}."
|
|
try:
|
|
vllm_version = f" vLLM: {importlib_version('vllm')}."
|
|
except:
|
|
vllm_version = ""
|
|
elif DEVICE_TYPE == "xpu":
|
|
gpu_stats = torch.xpu.get_device_properties(0)
|
|
gpu_stats_name = gpu_stats.name + ". " if gpu_stats.name != "" else "Intel XPU Device. "
|
|
gpu_version = torch.version.xpu
|
|
gpu_stats_snippet = f"Intel Toolkit: {gpu_version}."
|
|
# [TODO] After adding vLLM support for XPU, change this
|
|
vllm_version = ""
|
|
else:
|
|
raise ValueError(f"Unsloth: Unsupported device type: {DEVICE_TYPE}")
|
|
|
|
max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)
|
|
|
|
arch_name = model_type_arch.title()
|
|
arch_name = arch_name.replace("_Vl_", "_VL_").replace("_Moe", "_MoE")
|
|
statistics = (
|
|
f"==((====))== Unsloth {__version__}: Fast {arch_name} patching. Transformers: {transformers_version}.{vllm_version}\n"
|
|
f" {chr(92)}{chr(92)} /| {gpu_stats_name}Num GPUs = {DEVICE_COUNT}. Max memory: {max_memory} GB. Platform: {platform_system}.\n"
|
|
f"O^O/ {chr(92)}_/ {chr(92)} Torch: {torch.__version__}. {gpu_stats_snippet} Triton: {triton_version}\n"
|
|
f"{chr(92)} / Bfloat16 = {str(SUPPORTS_BFLOAT16).upper()}. FA [Xformers = {xformers_version}. FA2 = {HAS_FLASH_ATTENTION}]\n"
|
|
f' "-____-" Free license: http://github.com/unslothai/unsloth'
|
|
)
|
|
|
|
print(statistics)
|
|
|
|
# Warn about fast transfers
|
|
if "HF_HUB_ENABLE_HF_TRANSFER" in os.environ:
|
|
old_hf_transfer = os.environ["HF_HUB_ENABLE_HF_TRANSFER"]
|
|
if old_hf_transfer in ("False", "false"):
|
|
old_hf_transfer = "0"
|
|
if old_hf_transfer in ("True", "true"):
|
|
old_hf_transfer = "1"
|
|
else:
|
|
old_hf_transfer = "0"
|
|
if old_hf_transfer == "1":
|
|
print(
|
|
"Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!"
|
|
)
|
|
if old_hf_transfer != "0":
|
|
os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
|
|
|
|
# For debugging - we use a download counter to see if environments are not breaking or if HF is down
|
|
get_statistics(kwargs.get("local_files_only", False))
|
|
|
|
# The base + tokenizer prefetch runs AFTER the load-mode validation below, so an invalid
|
|
# load_in_* combination fails without first downloading a snapshot.
|
|
|
|
if dtype is None:
|
|
dtype = torch.float16 if not SUPPORTS_BFLOAT16 else torch.bfloat16
|
|
elif os.environ.get("UNSLOTH_FORCE_FLOAT32", "0") == "1":
|
|
if dtype == torch.float16:
|
|
dtype = 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)
|
|
|
|
bnb_compute_dtype = dtype
|
|
do_forced_float32 = False
|
|
if os.environ.get("UNSLOTH_FORCE_FLOAT32", "0") == "1":
|
|
print(
|
|
f"Unsloth: Using float16 precision for {model_type_arch} won't work! Using float32."
|
|
)
|
|
bnb_compute_dtype = torch.float16
|
|
do_forced_float32 = True
|
|
|
|
# Check for custom data-types
|
|
custom_datatype = None
|
|
correct_dtype = None
|
|
if os.environ.get("UNSLOTH_FORCE_CUSTOM_DTYPE", "") != "":
|
|
custom_datatype = os.environ["UNSLOTH_FORCE_CUSTOM_DTYPE"]
|
|
assert custom_datatype.count(";") >= 4
|
|
checker, _dtype, _bnb_compute_dtype, _custom_datatype, execute_code = (
|
|
custom_datatype.split(";", 4)
|
|
)
|
|
# Allow custom dtypes on all runs
|
|
allow_all_runs = checker == "all"
|
|
# Allow only on float16 datatypes
|
|
allow_float16_runs = (checker == "float16" or checker == "torch.float16") and (
|
|
dtype == torch.float16 or os.environ.get("UNSLOTH_FORCE_FLOAT32", "0") == "1"
|
|
)
|
|
if allow_all_runs or allow_float16_runs:
|
|
if eval(_dtype) is not None:
|
|
dtype = eval(_dtype)
|
|
if eval(_bnb_compute_dtype) is not None:
|
|
bnb_compute_dtype = eval(_bnb_compute_dtype)
|
|
correct_dtype = bnb_compute_dtype
|
|
custom_datatype = _custom_datatype
|
|
# Execute code as well
|
|
if len(execute_code.strip()) != 0:
|
|
exec(execute_code)
|
|
else:
|
|
custom_datatype = None
|
|
correct_dtype = None
|
|
|
|
if auto_config is None:
|
|
auto_config = AutoConfig.from_pretrained(
|
|
model_name,
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
local_files_only = local_files_only,
|
|
)
|
|
model_class = resolve_model_class(auto_model, auto_config)
|
|
attn_impl = resolve_attention_implementation(
|
|
model_class,
|
|
auto_config,
|
|
requested_attn_implementation = kwargs.get("attn_implementation", None),
|
|
supports_sdpa = supports_sdpa,
|
|
)
|
|
|
|
# Handle FP8 models: get_model_name has already redirected this to BF16 sibling if the model ships with
|
|
# FP8 weights. We just need to update it here for sanity.
|
|
auto_config.model_name = model_name
|
|
kwargs["attn_implementation"] = attn_impl
|
|
|
|
bnb_config = None
|
|
user_quantization_config = kwargs.get("quantization_config", None)
|
|
|
|
# Check if model already has a non-bitsandbytes quantization config (e.g. compressed-tensors/NVFP4)
|
|
from .loader_utils import (
|
|
check_and_disable_bitsandbytes_loading,
|
|
sync_unsloth_model_name_bnb_flags,
|
|
)
|
|
|
|
load_in_4bit, load_in_8bit, _ = check_and_disable_bitsandbytes_loading(
|
|
auto_config, load_in_4bit = load_in_4bit, load_in_8bit = load_in_8bit
|
|
)
|
|
# Correct UNSLOTH_MODEL_NAME's bnb tokens now that the effective bnb state is known
|
|
# (the per-load env was built before remap/disable). gpt-oss only; no-op otherwise.
|
|
sync_unsloth_model_name_bnb_flags(load_in_4bit, load_in_8bit)
|
|
|
|
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_16bit = False
|
|
|
|
if int(load_in_4bit) + int(load_in_8bit) + int(load_in_16bit) >= 2:
|
|
raise RuntimeError(
|
|
"Unsloth: Can only load in 4bit or 8bit or 16bit, not a combination!"
|
|
)
|
|
|
|
# Prefetch the repo (killable child) so the in-process load below is a cache hit. vLLM owns the
|
|
# weight download only when actually available; if fast_inference was requested but vLLM is
|
|
# missing, the load falls through in-process, so weights must still be warmed here.
|
|
_vllm_owns_weights = fast_inference and is_vLLM_available()
|
|
_prefetched = maybe_prefetch_hf_snapshot(
|
|
model_name,
|
|
token = token,
|
|
revision = kwargs.get("revision"),
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = kwargs.get("local_files_only", False),
|
|
fast_inference = _vllm_owns_weights,
|
|
subfolder = kwargs.get("subfolder"),
|
|
force_download = kwargs.get("force_download", False),
|
|
use_safetensors = kwargs.get("use_safetensors"),
|
|
from_tf = kwargs.get("from_tf", False),
|
|
from_flax = kwargs.get("from_flax", False),
|
|
# Bare load reads only ROOT weights; skip subdir weights. Ignored when a subfolder is set.
|
|
weights_at_root = True,
|
|
variant = kwargs.get("variant"), # forward so the warm keeps the variant .bin
|
|
gguf_file = kwargs.get(
|
|
"gguf_file"
|
|
), # forward so the warm fetches the GGUF (else ignored)
|
|
)
|
|
# 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
|
|
|
|
# Warm a SEPARATE tokenizer repo only (model_name is covered above). Not model_name here: this
|
|
# runs before fast_inference_setup may remap the repo, so it would warm the wrong one.
|
|
_tokenizer_repo = (
|
|
tokenizer_name if (isinstance(tokenizer_name, str) and tokenizer_name) else model_name
|
|
)
|
|
_warm_tokenizer_repo = (
|
|
isinstance(_tokenizer_repo, str)
|
|
and bool(_tokenizer_repo)
|
|
and _tokenizer_repo != model_name
|
|
)
|
|
if _warm_tokenizer_repo:
|
|
maybe_prefetch_hf_snapshot(
|
|
_tokenizer_repo,
|
|
token = token,
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = kwargs.get("local_files_only", False),
|
|
tokenizer_only = True,
|
|
)
|
|
|
|
_skip_modules = SKIP_QUANTIZATION_MODULES.copy()
|
|
# Nemotron-H uses 'mixer' (not 'mamba') for Mamba layers.
|
|
# Mamba fused kernels pass out_proj.weight directly to F.linear,
|
|
# which fails with quantized Params4bit. Skip out_proj from quantization.
|
|
if any(mt == "nemotron_h" for mt in (model_types or [])):
|
|
_skip_modules.append("out_proj")
|
|
|
|
if load_in_4bit:
|
|
bnb_config = BitsAndBytesConfig(
|
|
load_in_4bit = True,
|
|
bnb_4bit_use_double_quant = True,
|
|
bnb_4bit_quant_type = "nf4",
|
|
bnb_4bit_compute_dtype = bnb_compute_dtype,
|
|
llm_int8_skip_modules = _skip_modules,
|
|
)
|
|
elif load_in_8bit:
|
|
bnb_config = BitsAndBytesConfig(
|
|
load_in_8bit = True,
|
|
llm_int8_skip_modules = _skip_modules,
|
|
)
|
|
elif load_in_16bit:
|
|
bnb_config = None
|
|
elif not load_in_4bit and not load_in_8bit and not full_finetuning:
|
|
print("Unsloth: QLoRA and full finetuning all not selected. Switching to 16bit LoRA.")
|
|
|
|
if full_finetuning:
|
|
os.environ["UNSLOTH_ENABLE_FULL_FINETUNING"] = "1"
|
|
if dtype == torch.bfloat16:
|
|
if float32_mixed_precision != True:
|
|
print(
|
|
f"Unsloth: Using bfloat16 full finetuning which cuts memory usage by 50%.\n"
|
|
f"To enable float32 training, use `float32_mixed_precision = True` during FastLanguageModel.from_pretrained"
|
|
)
|
|
else:
|
|
print(
|
|
f"Unsloth: Using full float32 full finetuning. "
|
|
f"To enable bfloat16 training to reduce VRAM usage by 50% albeit with a slightly higher loss, do:\n"
|
|
"use `float32_mixed_precision = False` during FastLanguageModel.from_pretrained"
|
|
)
|
|
os.environ["UNSLOTH_BFLOAT16_MIXED_PRECISION"] = "1"
|
|
else:
|
|
print(
|
|
"Unsloth: Float16 full finetuning uses more memory since we upcast weights to float32."
|
|
)
|
|
else:
|
|
os.environ["UNSLOTH_ENABLE_FULL_FINETUNING"] = "0"
|
|
|
|
# Fix AttributeError: 'BitsAndBytesConfig' object has no attribute 'get_loading_attributes'
|
|
if bnb_config is not None and not hasattr(bnb_config, "get_loading_attributes"):
|
|
bnb_config.get_loading_attributes = lambda *args, **kwargs: {}
|
|
|
|
# Cannot be None, since HF now checks for the config
|
|
if load_in_4bit or load_in_8bit:
|
|
# Ignore load_in_4bit / load_in_8bit for MXFP4 - best to get config file
|
|
if "gpt-oss-20b" in model_name.lower() or "gpt-oss-120b" in model_name.lower():
|
|
pass
|
|
else:
|
|
if user_quantization_config is None:
|
|
kwargs["quantization_config"] = bnb_config
|
|
else:
|
|
if auto_config is None:
|
|
auto_config = AutoConfig.from_pretrained(
|
|
model_name,
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
local_files_only = local_files_only,
|
|
)
|
|
if hasattr(auto_config, "quantization_config"):
|
|
from transformers.quantizers.auto import (
|
|
AUTO_QUANTIZATION_CONFIG_MAPPING,
|
|
)
|
|
|
|
quantization_config = auto_config.quantization_config
|
|
quant_method = quantization_config["quant_method"]
|
|
# Sometimes bitsandbytes_4bit + bitsandbytes_8bit is provided
|
|
if (
|
|
quant_method == "bitsandbytes"
|
|
and "bitsandbytes" not in AUTO_QUANTIZATION_CONFIG_MAPPING
|
|
):
|
|
if "bitsandbytes_4bit" not in AUTO_QUANTIZATION_CONFIG_MAPPING:
|
|
raise KeyError(
|
|
"Unsloth: AUTO_QUANTIZATION_CONFIG_MAPPING does not have `bitsandbytes_4bit`"
|
|
)
|
|
quantizer = AUTO_QUANTIZATION_CONFIG_MAPPING["bitsandbytes_4bit"]
|
|
else:
|
|
quantizer = AUTO_QUANTIZATION_CONFIG_MAPPING[quant_method]
|
|
quantizer_kwargs = {}
|
|
if quant_method == "compressed-tensors":
|
|
# Ignore these
|
|
pass
|
|
else:
|
|
# We cannot dequantize since gpt-oss-20b MXFP4 will now be gpt-oss-20b-BF16
|
|
if load_in_16bit and "dequantize" in inspect.signature(quantizer).parameters:
|
|
quantizer_kwargs["dequantize"] = True
|
|
try:
|
|
# Sometimes this fails so we wrap it in a try except
|
|
quantization_config = quantizer.from_dict(
|
|
quantization_config, **quantizer_kwargs
|
|
)
|
|
except:
|
|
pass
|
|
if user_quantization_config is None:
|
|
kwargs["quantization_config"] = quantization_config
|
|
|
|
# Check if using forced float32 - we load it in bfloat16, then cast to float16!
|
|
torch_dtype = dtype
|
|
if do_forced_float32:
|
|
torch_dtype = torch.bfloat16
|
|
|
|
kwargs = add_dtype_kwargs(torch_dtype, kwargs)
|
|
|
|
config_attn_impl = kwargs.get("attn_implementation", None)
|
|
if config_attn_impl is None:
|
|
config_attn_impl = "sdpa" if supports_sdpa else "eager"
|
|
if auto_config is None:
|
|
auto_config = AutoConfig.from_pretrained(
|
|
model_name,
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
local_files_only = local_files_only,
|
|
)
|
|
_set_attn_impl(auto_config, config_attn_impl)
|
|
model_config = auto_config
|
|
|
|
verify_fp8_support_if_applicable(model_config)
|
|
|
|
raise_handler = RaiseUninitialized()
|
|
try:
|
|
if offload_embedding and fast_inference:
|
|
# vLLM manages its own weights; embedding offload does not apply.
|
|
print(
|
|
"Unsloth: Not offloading embeddings; incompatible with fast_inference (vLLM)."
|
|
)
|
|
offload_embedding = False
|
|
if not fast_inference:
|
|
# Prevent load_in_fp8 from being forwarded into HF internal model loading
|
|
load_in_fp8 = kwargs.pop("load_in_fp8", None)
|
|
# Transformers 5.x @strict config classes reject unexpected kwargs.
|
|
# Move config-level attributes onto the config object directly.
|
|
_num_labels = kwargs.pop("num_labels", None)
|
|
if _num_labels is not None:
|
|
set_task_config_attr(model_config, "num_labels", _num_labels)
|
|
for _cfg_key in ("id2label", "label2id", "problem_type"):
|
|
_cfg_val = kwargs.pop(_cfg_key, None)
|
|
if _cfg_val is not None:
|
|
set_task_config_attr(model_config, _cfg_key, _cfg_val)
|
|
_cfg_val = kwargs.pop("max_position_embeddings", None)
|
|
if _cfg_val is not None:
|
|
setattr(model_config, "max_position_embeddings", _cfg_val)
|
|
model = auto_model.from_pretrained(
|
|
model_name,
|
|
config = model_config,
|
|
device_map = device_map,
|
|
# torch_dtype = torch_dtype, # Transformers removed torch_dtype
|
|
# quantization_config = bnb_config,
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
# attn_implementation = attn_implementation,
|
|
**kwargs,
|
|
)
|
|
# Attach dispatch hooks for bnb multi-device loads.
|
|
_attach_bnb_multidevice_hooks(
|
|
model,
|
|
load_in_4bit = load_in_4bit,
|
|
load_in_8bit = load_in_8bit,
|
|
offload_embedding = offload_embedding,
|
|
fast_inference = fast_inference,
|
|
)
|
|
# Re-apply block-fp8 weight_scale_inv tensors transformers dropped on load (#6200).
|
|
_restore_dropped_fp8_scales(
|
|
model,
|
|
model_name,
|
|
local_files_only = local_files_only,
|
|
token = token,
|
|
revision = kwargs.get("revision"),
|
|
subfolder = kwargs.get("subfolder"),
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
variant = kwargs.get("variant"),
|
|
)
|
|
if hasattr(model, "generate"):
|
|
model.fast_generate = make_fast_generate_wrapper(model.generate)
|
|
model.fast_generate_batches = error_out_no_vllm
|
|
if offload_embedding:
|
|
if bool(os.environ.get("WSL_DISTRO_NAME") or os.environ.get("WSL_INTEROP")):
|
|
# WSL doesn't work with offloaded embeddings
|
|
pass
|
|
elif os.name == "nt":
|
|
# Windows doesn't work with offloaded embeddings
|
|
pass
|
|
else:
|
|
embed_tokens = model.get_input_embeddings()
|
|
out_embed = (
|
|
model.get_output_embeddings()
|
|
if hasattr(model, "get_output_embeddings")
|
|
else None
|
|
)
|
|
if _embeddings_are_tied(embed_tokens, out_embed):
|
|
raise NotImplementedError(
|
|
"offload_embedding = True is not supported for models with tied word "
|
|
"embeddings (embed_tokens shares its weight with lm_head). Offloading "
|
|
"would strand the output projection on CPU and saves no VRAM. Set "
|
|
"offload_embedding = False for this model."
|
|
)
|
|
nbytes = embed_tokens.weight.numel() * embed_tokens.weight.itemsize
|
|
ngb = round(nbytes / 1024 / 1024 / 1024, 2)
|
|
print(f"Unsloth: Offloading embeddings to RAM to save {ngb} GB.")
|
|
_embed_device = embed_tokens.weight.device # decoder device, before offload
|
|
embed_tokens.to("cpu")
|
|
|
|
# Device-safe embedding offload.
|
|
_install_offload_embedding_hooks(embed_tokens, out_embed, _embed_device)
|
|
# Must free GPU memory otherwise will not free!
|
|
torch.cuda.empty_cache()
|
|
gc.collect()
|
|
else:
|
|
from unsloth_zoo.vllm_utils import (
|
|
load_vllm,
|
|
get_vllm_state_dict,
|
|
convert_vllm_to_huggingface,
|
|
generate_batches,
|
|
get_lora_supported_ranks,
|
|
)
|
|
|
|
if full_finetuning:
|
|
max_lora_rank = max(get_lora_supported_ranks())
|
|
raise NotImplementedError(
|
|
"Unsloth: `fast_inference=True` cannot be used together with `full_finetuning=True`.\n"
|
|
"Reason: fast_inference is optimized for inference-only workflows and "
|
|
"does not currently support full fine-tuning.\n"
|
|
"Workaround: disable fast_inference, or use parameter-efficient fine-tuning "
|
|
f"(e.g. LoRA with rank r={max_lora_rank})."
|
|
)
|
|
|
|
model_config.model_name = model_name
|
|
|
|
if fast_inference:
|
|
fast_inference, model_name = fast_inference_setup(model_name, model_config)
|
|
|
|
fp8_mode = None
|
|
if 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,
|
|
)
|
|
|
|
allowed_args = inspect.getfullargspec(load_vllm).args
|
|
load_vllm_kwargs = dict(
|
|
model_name = model_name,
|
|
config = model_config,
|
|
gpu_memory_utilization = gpu_memory_utilization,
|
|
max_seq_length = max_seq_length,
|
|
dtype = dtype,
|
|
float8_kv_cache = float8_kv_cache,
|
|
enable_lora = vllm_enable_lora,
|
|
max_lora_rank = max_lora_rank,
|
|
disable_log_stats = disable_log_stats,
|
|
use_bitsandbytes = load_in_4bit,
|
|
unsloth_vllm_standby = unsloth_vllm_standby,
|
|
is_vision_model = is_vlm_config,
|
|
fp8_mode = fp8_mode,
|
|
)
|
|
for allowed_arg in allowed_args:
|
|
if allowed_arg not in load_vllm_kwargs and allowed_arg in kwargs:
|
|
load_vllm_kwargs[allowed_arg] = kwargs[allowed_arg]
|
|
|
|
# Load vLLM first
|
|
llm = load_vllm(**load_vllm_kwargs)
|
|
|
|
# Convert to HF format
|
|
_, quant_state_dict = get_vllm_state_dict(
|
|
llm,
|
|
config = model_config,
|
|
is_vision_model = is_vlm_config,
|
|
load_in_fp8 = load_in_fp8,
|
|
)
|
|
model = convert_vllm_to_huggingface(
|
|
quant_state_dict,
|
|
model_config,
|
|
dtype,
|
|
bnb_config,
|
|
is_vision_model = is_vlm_config,
|
|
)
|
|
model.vllm_engine = llm
|
|
llm.shared_weights = True
|
|
model.fast_generate = model.vllm_engine.generate
|
|
model.fast_generate_batches = functools.partial(generate_batches, model.vllm_engine)
|
|
|
|
finally:
|
|
raise_handler.remove()
|
|
# Return old flag
|
|
os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = old_hf_transfer
|
|
|
|
# Check float32 norm weights
|
|
if os.environ.get("UNSLOTH_HIGH_PRECISION_LAYERNORM", "0") == "1":
|
|
for jj, (name, module) in enumerate(model.named_modules()):
|
|
if (
|
|
name.endswith(("norm", "norm1", "norm2", "norm3", "norm4"))
|
|
or "layernorm" in name
|
|
or "layer_norm" in name
|
|
) and hasattr(module, "weight"):
|
|
module._pre_set_compute_dtype = torch.float32
|
|
# Edit data-types
|
|
if custom_datatype is not None:
|
|
with torch.no_grad():
|
|
for jj, (name, module) in enumerate(model.named_modules()):
|
|
exec(custom_datatype)
|
|
# Clear deleted GPU items
|
|
for _ in range(3):
|
|
gc.collect()
|
|
if DEVICE_TYPE in ("cuda", "hip"):
|
|
torch.cuda.empty_cache()
|
|
elif DEVICE_TYPE == "xpu":
|
|
torch.xpu.empty_cache()
|
|
|
|
# Counteract saved tokenizers
|
|
tokenizer_name = model_name if tokenizer_name is None else tokenizer_name
|
|
|
|
# On the vLLM path the tokenizer warm was deferred (fast_inference_setup may remap model_name).
|
|
# Warm the now-final tokenizer repo so the load below hits the cache (a cached/local repo is a no-op).
|
|
if _vllm_owns_weights and isinstance(tokenizer_name, str) and tokenizer_name:
|
|
maybe_prefetch_hf_snapshot(
|
|
tokenizer_name,
|
|
token = token,
|
|
revision = kwargs.get("revision"),
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = kwargs.get("local_files_only", False),
|
|
tokenizer_only = True,
|
|
)
|
|
|
|
# Fix _Unsloth_Patched_ prefix in local config files from old saves (issue #4085)
|
|
if os.path.isdir(tokenizer_name):
|
|
import json as _json
|
|
for _cfg_name in (
|
|
"processor_config.json",
|
|
"preprocessor_config.json",
|
|
"tokenizer_config.json",
|
|
):
|
|
_cfg_path = os.path.join(tokenizer_name, _cfg_name)
|
|
if os.path.exists(_cfg_path):
|
|
try:
|
|
with open(_cfg_path, "r", encoding = "utf-8") as _f:
|
|
_cfg = _json.load(_f)
|
|
if _cfg.get("processor_class", "").startswith("_Unsloth_Patched_"):
|
|
_cfg["processor_class"] = _cfg["processor_class"][
|
|
len("_Unsloth_Patched_") :
|
|
]
|
|
with open(_cfg_path, "w", encoding = "utf-8") as _f:
|
|
_json.dump(_cfg, _f, indent = 2, ensure_ascii = False)
|
|
except Exception:
|
|
pass
|
|
|
|
# Functional load chain (AutoProcessor -> get_auto_processor -> manual VLM
|
|
# fallback); offline is already forced upstream. Surfaces the error for the retry.
|
|
def _acquire_processor(lfo):
|
|
_err = None # underlying load failure (used by the entry-point retry)
|
|
if (whisper_language and whisper_task) or auto_model.__name__.endswith(
|
|
"ForConditionalGeneration"
|
|
):
|
|
try:
|
|
_tok = auto_processor.from_pretrained(
|
|
tokenizer_name,
|
|
padding_side = "left",
|
|
token = token,
|
|
language = whisper_language,
|
|
task = whisper_task,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = lfo,
|
|
)
|
|
except Exception as _e:
|
|
_tok = None
|
|
_err = _e
|
|
else:
|
|
try:
|
|
_tok = auto_processor.from_pretrained(
|
|
tokenizer_name,
|
|
padding_side = "left",
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = lfo,
|
|
)
|
|
except Exception as _e:
|
|
_err = _e
|
|
try:
|
|
_tok = get_auto_processor(
|
|
tokenizer_name,
|
|
padding_side = "left",
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = lfo,
|
|
)
|
|
except Exception:
|
|
# Swallow so the manual fallback / entry-point retry can run.
|
|
_tok = None
|
|
|
|
# Build the processor manually if it failed to load or silently degraded to
|
|
# a text-only tokenizer (no image_processor) for a VLM (issue #4085).
|
|
_processor_is_degraded = (
|
|
is_vlm and _tok is not None and not hasattr(_tok, "image_processor")
|
|
)
|
|
if (_tok is None or _processor_is_degraded) and is_vlm:
|
|
try:
|
|
_fallback, _fb_err = _construct_vlm_processor_fallback(
|
|
tokenizer_name,
|
|
model_type_arch,
|
|
token,
|
|
trust_remote_code,
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = lfo,
|
|
)
|
|
except Exception as _fe:
|
|
_fallback, _fb_err = None, _fe
|
|
if _fallback is not None:
|
|
_tok = _fallback
|
|
elif _err is None or (_fb_err is not None and _is_offline_related_error(_fb_err)):
|
|
# Prefer a network fallback error over a permanent primary one so the
|
|
# offline retry still fires.
|
|
_err = _fb_err
|
|
return _tok, _err
|
|
|
|
def _is_degraded_vlm(_t):
|
|
# VLM that loaded only a text-only tokenizer (no image_processor).
|
|
return is_vlm and _t is not None and not hasattr(_t, "image_processor")
|
|
|
|
tokenizer, _primary_err = _acquire_processor(local_files_only)
|
|
# Online network failure/degrade: raise so @_offline_aware_load retries from cache.
|
|
# Permanent / missing-file errors propagate; when already offline keep what we got.
|
|
if (
|
|
(tokenizer is None or _is_degraded_vlm(tokenizer))
|
|
and not local_files_only
|
|
and _is_offline_related_error(_primary_err)
|
|
):
|
|
raise _primary_err
|
|
# Missing torchvision silently degrades a VLM processor to text-only; surface the
|
|
# real cause instead of a later collator error (#4202), incl. on a silent degrade.
|
|
if is_vlm and (tokenizer is None or not hasattr(tokenizer, "image_processor")):
|
|
if _missing_torchvision_error(_primary_err):
|
|
raise ImportError(
|
|
f"Unsloth: Could not load the vision processor for `{tokenizer_name}` "
|
|
"because torchvision is not installed. transformers requires torchvision "
|
|
"for this model's vision (image/video) processors. Please install it, "
|
|
"e.g. `pip install torchvision`."
|
|
)
|
|
import sys
|
|
print(
|
|
f"Unsloth: Warning - VLM processor fallback returned None for model_type={model_type_arch}",
|
|
file = sys.stderr,
|
|
)
|
|
# Backwards compat: if processor has no chat_template (e.g. old saves without
|
|
# chat_template.jinja) but the inner tokenizer does, copy it to the processor.
|
|
if (
|
|
hasattr(tokenizer, "tokenizer")
|
|
and getattr(tokenizer, "chat_template", None) is None
|
|
and getattr(tokenizer.tokenizer, "chat_template", None) is not None
|
|
):
|
|
tokenizer.chat_template = tokenizer.tokenizer.chat_template
|
|
|
|
if hasattr(tokenizer, "tokenizer"):
|
|
__tokenizer = tokenizer.tokenizer
|
|
# Add padding side as well
|
|
__tokenizer.padding_side = "left"
|
|
# Check bos, eos, pad tokens
|
|
if hasattr(__tokenizer, "bos_token"):
|
|
tokenizer.bos_token = __tokenizer.bos_token
|
|
tokenizer.bos_token_id = __tokenizer.bos_token_id
|
|
if hasattr(__tokenizer, "eos_token"):
|
|
tokenizer.eos_token = __tokenizer.eos_token
|
|
tokenizer.eos_token_id = __tokenizer.eos_token_id
|
|
if hasattr(__tokenizer, "pad_token"):
|
|
tokenizer.pad_token = __tokenizer.pad_token
|
|
tokenizer.pad_token_id = __tokenizer.pad_token_id
|
|
# Fix other stuff like BnB compute data types
|
|
model, tokenizer = patch_model_and_tokenizer(
|
|
model,
|
|
tokenizer,
|
|
downcast_rope = False,
|
|
fix_embeddings = False,
|
|
do_forced_float32 = do_forced_float32,
|
|
correct_dtype = correct_dtype,
|
|
)
|
|
|
|
try:
|
|
model, tokenizer = patch_tokenizer(model, tokenizer)
|
|
except Exception as _patch_err:
|
|
# Some VLM processors (e.g. ERNIE VL) fail patching; fall back to AutoTokenizer.
|
|
try:
|
|
from transformers import AutoTokenizer as _AutoTokenizer
|
|
|
|
_fallback_tok = _AutoTokenizer.from_pretrained(
|
|
tokenizer_name,
|
|
padding_side = "left",
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = local_files_only,
|
|
)
|
|
model, _fallback_tok = patch_tokenizer(model, _fallback_tok)
|
|
# Re-attach as processor wrapper if original was a processor
|
|
if hasattr(tokenizer, "image_processor"):
|
|
tokenizer.tokenizer = _fallback_tok
|
|
else:
|
|
tokenizer = _fallback_tok
|
|
except Exception as _fb_err:
|
|
# Online network failure: propagate for the offline retry; else raise the patch error.
|
|
if not local_files_only and _is_offline_related_error(_fb_err):
|
|
raise
|
|
raise _patch_err
|
|
model = post_patch_loss_function(model)
|
|
|
|
# Log Unsloth version for future fastpaths for inference
|
|
if hasattr(model, "config"):
|
|
model.config.update({"unsloth_version": __version__})
|
|
patch_saving_functions(model, vision = True)
|
|
if tokenizer is None:
|
|
# Last resort: AutoTokenizer, then PreTrainedTokenizerFast (raise on network failure to retry).
|
|
def _last_resort_tokenizer(lfo):
|
|
from transformers import AutoTokenizer as _AutoTokenizer
|
|
try:
|
|
return _AutoTokenizer.from_pretrained(
|
|
tokenizer_name,
|
|
padding_side = "left",
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = lfo,
|
|
)
|
|
except Exception:
|
|
from transformers import PreTrainedTokenizerFast
|
|
return PreTrainedTokenizerFast.from_pretrained(
|
|
tokenizer_name,
|
|
padding_side = "left",
|
|
token = token,
|
|
trust_remote_code = trust_remote_code,
|
|
cache_dir = kwargs.get("cache_dir"),
|
|
local_files_only = lfo,
|
|
)
|
|
|
|
_last_resort_err = None
|
|
try:
|
|
tokenizer = _last_resort_tokenizer(local_files_only)
|
|
except Exception as _e:
|
|
_last_resort_err = _e
|
|
# Online network failure: let the entry point retry forced-offline.
|
|
if not local_files_only and _is_offline_related_error(_e):
|
|
raise
|
|
if tokenizer is None:
|
|
del model
|
|
raise RuntimeError(
|
|
"Unsloth: Could not load the tokenizer/processor. If you are "
|
|
"offline, make sure the tokenizer files exist in the checkpoint "
|
|
"folder or were previously downloaded to the Hugging Face cache, "
|
|
"or set HF_HUB_OFFLINE=1 to force local loading. "
|
|
"Otherwise please check that the model has a tokenizer."
|
|
) from _last_resort_err
|
|
patch_saving_functions(tokenizer, vision = True)
|
|
|
|
# Fix gradient accumulation. See issue #4982.
|
|
from transformers.trainer import Trainer
|
|
|
|
apply_accepts_loss_kwargs_fix(model)
|
|
patch_gradient_accumulation_fix(Trainer)
|
|
|
|
# Save tokenizer for inference purposes
|
|
tokenizer.padding_side = "left" # Force inference
|
|
if hasattr(tokenizer, "tokenizer"):
|
|
tokenizer.tokenizer.padding_side = "left" # Force inference
|
|
# Audio feature extractors must stay right padded: left (a text setting,
|
|
# forwarded by from_pretrained) shifts Whisper mels and desyncs Gemma 4
|
|
# audio token counts (crash on transformers < 5.10).
|
|
feature_extractor = getattr(tokenizer, "feature_extractor", None)
|
|
if (
|
|
feature_extractor is not None
|
|
and getattr(feature_extractor, "padding_side", None) == "left"
|
|
):
|
|
feature_extractor.padding_side = "right"
|
|
m = model
|
|
while hasattr(m, "model"):
|
|
m.max_seq_length = max_seq_length
|
|
m._saved_temp_tokenizer = tokenizer
|
|
m = m.model
|
|
m.max_seq_length = max_seq_length
|
|
# Save to modules as well
|
|
for module in model.modules():
|
|
module.max_seq_length = max_seq_length
|
|
m._saved_temp_tokenizer = tokenizer
|
|
# Prevent Transformers Trainer from auto-wrapping Unsloth LoRA models in DP.
|
|
_mark_unsloth_disable_data_parallel(model, disable = not full_finetuning)
|
|
|
|
# Patch generate
|
|
if os.environ.get("UNSLOTH_DISABLE_FAST_GENERATION", "0") == "0" and hasattr(
|
|
model, "generate"
|
|
):
|
|
if model.generate.__name__ != "unsloth_base_fast_generate":
|
|
model._old_generate = model.generate
|
|
unsloth_base_fast_generate.__doc__ = model._old_generate.__doc__
|
|
model.generate = types.MethodType(unsloth_base_fast_generate, model)
|
|
model._unsloth_trust_remote_code = trust_remote_code
|
|
# Post patches
|
|
model = FastBaseModel.post_patch_model(
|
|
model,
|
|
use_gradient_checkpointing = use_gradient_checkpointing,
|
|
trust_remote_code = trust_remote_code,
|
|
model_type = model_type_arch,
|
|
tokenizer = tokenizer,
|
|
float32_mixed_precision = float32_mixed_precision,
|
|
)
|
|
# Clear deleted GPU items
|
|
for _ in range(3):
|
|
gc.collect()
|
|
if DEVICE_TYPE in ("cuda", "hip"):
|
|
torch.cuda.empty_cache()
|
|
elif DEVICE_TYPE == "xpu":
|
|
torch.xpu.empty_cache()
|
|
return model, tokenizer
|
|
|
|
@staticmethod
|
|
def get_peft_model(
|
|
model,
|
|
r = 16,
|
|
target_modules = None,
|
|
lora_alpha = 16,
|
|
lora_dropout = 0.0,
|
|
bias = "none",
|
|
finetune_vision_layers = True,
|
|
finetune_language_layers = True,
|
|
finetune_attention_modules = True,
|
|
finetune_mlp_modules = True,
|
|
finetune_last_n_layers = None,
|
|
layers_to_transform = None,
|
|
layers_pattern = None,
|
|
use_gradient_checkpointing = "unsloth",
|
|
random_state = 3407,
|
|
max_seq_length = 2048, # not used anymore
|
|
use_rslora = False,
|
|
modules_to_save = None,
|
|
init_lora_weights = True,
|
|
loftq_config = {},
|
|
task_type = TaskType.CAUSAL_LM,
|
|
temporary_location = "_unsloth_temporary_saved_buffers",
|
|
qat_scheme = None,
|
|
target_parameters = None, # For MoE expert layers (nn.Parameter)
|
|
ensure_weight_tying = False, # [TODO] Add `ensure_weight_tying` for `modules_to_save` for vision models
|
|
finetune_audio_layers = False, # placed last to preserve existing positional argument order
|
|
**kwargs,
|
|
):
|
|
if os.environ.get("UNSLOTH_ENABLE_FULL_FINETUNING", "0") == "1":
|
|
print("Unsloth: Full finetuning is enabled, so .get_peft_model has no effect")
|
|
# Full finetuning still compiles, so a stray pre-train forward can poison the
|
|
# cache; install the detector here too (it is idempotent).
|
|
_unsloth_install_pretrain_detector(model)
|
|
return model
|
|
transformers_set_seed(random_state)
|
|
|
|
if type(r) is not int:
|
|
raise TypeError(f"Unsloth: Rank of {str(r)} must be an integer.")
|
|
if r <= 0:
|
|
raise TypeError(f"Unsloth: Rank of {str(r)} must be larger than 0.")
|
|
|
|
if isinstance(model, PeftModelForCausalLM):
|
|
raise RuntimeError("Unsloth: You already added LoRA adapters to your model!")
|
|
|
|
# Remember whether the CALLER explicitly opted into audio. "all-linear" turns
|
|
# the flag on implicitly below, but an old unsloth_zoo that cannot do audio
|
|
# must not make a plain all-linear (text/vision) run fail.
|
|
_audio_explicitly_requested = bool(finetune_audio_layers)
|
|
if target_modules == "all-linear":
|
|
finetune_vision_layers = True
|
|
finetune_language_layers = True
|
|
finetune_attention_modules = True
|
|
finetune_mlp_modules = True
|
|
finetune_audio_layers = True
|
|
# Older unsloth_zoo (before get_peft_regex gained finetune_audio_layers) does
|
|
# not accept the kwarg. Pass it only when supported; if the caller EXPLICITLY
|
|
# asked for audio but it is unsupported, fail loudly rather than silently
|
|
# training a language-only adapter. (all-linear's implicit opt-in degrades
|
|
# gracefully instead of raising.)
|
|
if "finetune_audio_layers" in inspect.signature(get_peft_regex).parameters:
|
|
_audio_kwargs = {"finetune_audio_layers": finetune_audio_layers}
|
|
elif _audio_explicitly_requested:
|
|
raise RuntimeError(
|
|
"Unsloth: finetune_audio_layers=True requires a newer unsloth_zoo. "
|
|
"Please upgrade with `pip install --upgrade --no-deps unsloth_zoo`."
|
|
)
|
|
else:
|
|
_audio_kwargs = {}
|
|
# Remember the caller's ORIGINAL explicit leaf list for MoE expert
|
|
# detection. When an explicit list is routed through get_peft_regex for
|
|
# family scoping below, the generated regex carries get_peft_regex's full
|
|
# "mlp|feed_forward|ffn|dense" component block even when the caller named
|
|
# only attention leaves (q/k/v/o_proj). Keying expert detection on that
|
|
# regex would train the experts for an attention-only request. The
|
|
# original list carries the true leaf intent, so use it for MoE detection;
|
|
# only the auto (None / "all-linear") path relies on the regex, whose mlp
|
|
# block is the sole remaining MLP-intent signal on fused-expert models.
|
|
_moe_detect_target = target_modules if type(target_modules) in (list, tuple) else None
|
|
if target_modules is None or target_modules == "all-linear":
|
|
target_modules = get_peft_regex(
|
|
model,
|
|
finetune_vision_layers = finetune_vision_layers,
|
|
finetune_language_layers = finetune_language_layers,
|
|
finetune_attention_modules = finetune_attention_modules,
|
|
finetune_mlp_modules = finetune_mlp_modules,
|
|
**_audio_kwargs,
|
|
)
|
|
else:
|
|
assert type(target_modules) in (list, tuple, str)
|
|
# Route an explicit list through get_peft_regex when the caller scoped a
|
|
# layer family (one of the finetune_* below is off) OR opted into audio (so
|
|
# the new audio/embedder branches are considered). finetune_audio_layers is
|
|
# a POSITIVE term here: using `not finetune_audio_layers` would -- since it
|
|
# defaults False -- force every explicit list through the filter.
|
|
_scoping = (
|
|
not finetune_vision_layers
|
|
or not finetune_language_layers
|
|
or not finetune_attention_modules
|
|
or not finetune_mlp_modules
|
|
)
|
|
if type(target_modules) in (list, tuple) and (_scoping or finetune_audio_layers):
|
|
if _scoping:
|
|
print(
|
|
"Unsloth: Explicit target_modules are constrained by the "
|
|
"finetune_(vision|language|attention|mlp) filters; adapters "
|
|
"attach only where both select."
|
|
)
|
|
target_modules = get_peft_regex(
|
|
model,
|
|
finetune_vision_layers = finetune_vision_layers,
|
|
finetune_language_layers = finetune_language_layers,
|
|
finetune_attention_modules = finetune_attention_modules,
|
|
finetune_mlp_modules = finetune_mlp_modules,
|
|
target_modules = list(target_modules),
|
|
**_audio_kwargs,
|
|
)
|
|
|
|
if hasattr(model, "vllm_engine"):
|
|
if (
|
|
hasattr(model.vllm_engine, "llm_engine")
|
|
and hasattr(model.vllm_engine.llm_engine, "vllm_config")
|
|
and getattr(model.vllm_engine.llm_engine.vllm_config, "lora_config", None) is None
|
|
):
|
|
# If vLLM is being used but lora is not enabled, throw an error
|
|
# Ref https://github.com/vllm-project/vllm/blob/51ba839555a5d122eadd91e9c16463ac288f5fa1/vllm/v1/engine/processor.py#L148-L151
|
|
raise RuntimeError("Unsloth: LoRA is not enabled for this model!")
|
|
if finetune_vision_layers:
|
|
# vLLM does not support LoRA on vision layers
|
|
# https://github.com/vllm-project/vllm/blob/main/vllm/lora/models.py#L471-L477
|
|
# TODO: Update this once vLLM V1 supports LoRA on vision layers (possibly not happening)
|
|
raise RuntimeError(
|
|
"Unsloth: Finetuning vision layers is not supported for fast_inference. Only text layers are supported!"
|
|
)
|
|
if model.config.model_type in VLLM_NON_LORA_VLM:
|
|
# mllama is still only in vllm v0 https://arc.net/l/quote/llwkfgmu
|
|
# https://docs.vllm.ai/en/stable/models/supported_models.html#text-generation_1
|
|
# vLLM V0 does not support LoRA on multi modal models.
|
|
# TODO: Update this once vLLM V1 supports Llama 3.2 aka mllama
|
|
raise RuntimeError(
|
|
"Unsloth: LoRA finetuning for Llama 3.2 aka mllama models is not supported with fast_inference!"
|
|
)
|
|
|
|
# Clear deleted GPU items
|
|
for _ in range(3):
|
|
gc.collect()
|
|
if DEVICE_TYPE in ("cuda", "hip"):
|
|
torch.cuda.empty_cache()
|
|
elif DEVICE_TYPE == "xpu":
|
|
torch.xpu.empty_cache()
|
|
max_seq_length = model.max_seq_length
|
|
# If we pass loftq_config = None we will get an error
|
|
loftq_config = validate_loftq_config(
|
|
loftq_config, lora_dropout, bias, init_lora_weights, model
|
|
)
|
|
|
|
# Auto-detect MoE models and populate target_parameters for expert layers.
|
|
# Prefer the caller's ORIGINAL explicit leaf list over the scoped regex so an
|
|
# attention-only request does not train experts via get_peft_regex's mlp block,
|
|
# but only when MLP and language families are both still in scope. If the caller
|
|
# scoped MLP or language OFF (finetune_mlp_modules / finetune_language_layers
|
|
# False), the scoped regex already dropped the experts, so honor it instead of
|
|
# re-introducing the original list's gate/up/down leaves.
|
|
if target_parameters is None:
|
|
_moe_targets = _select_moe_detection_targets(
|
|
_moe_detect_target,
|
|
target_modules,
|
|
finetune_mlp_modules = finetune_mlp_modules,
|
|
finetune_language_layers = finetune_language_layers,
|
|
)
|
|
target_parameters = get_moe_target_parameters(model, _moe_targets)
|
|
|
|
# Per-expert Linear expert layouts (e.g. gpt-oss bnb-4bit) target experts via
|
|
# target_modules, not fused Parameters. Extend either form PEFT accepts: a leaf
|
|
# list (explicit) or a regex string (auto / all-linear / scoped). No-op otherwise.
|
|
_moe_module_detect = _select_moe_detection_targets(
|
|
_moe_detect_target,
|
|
target_modules,
|
|
finetune_mlp_modules = finetune_mlp_modules,
|
|
finetune_language_layers = finetune_language_layers,
|
|
)
|
|
_moe_module_targets = get_moe_target_modules(model, _moe_module_detect)
|
|
if _moe_module_targets:
|
|
if isinstance(target_modules, (list, tuple)):
|
|
target_modules = list(target_modules) + [
|
|
target for target in _moe_module_targets if target not in target_modules
|
|
]
|
|
elif isinstance(target_modules, str):
|
|
_expert_leaves = sorted({t.rsplit(".", 1)[0] for t in _moe_module_targets})
|
|
_expert_alt = (
|
|
r".*\.experts\.(?:"
|
|
+ "|".join(re.escape(leaf) for leaf in _expert_leaves)
|
|
+ r")\.\d+"
|
|
)
|
|
target_modules = f"(?:{target_modules})|(?:{_expert_alt})"
|
|
print(
|
|
f"Unsloth: Detected MoE model with per-expert Linear experts. "
|
|
f"Enabling LoRA on {len(_moe_module_targets)} expert projection modules."
|
|
)
|
|
warn_if_zoo_cannot_merge_moe_experts()
|
|
|
|
if finetune_last_n_layers is not None and layers_to_transform is None:
|
|
_total_layers = _get_total_transformer_layers(model)
|
|
if _total_layers is not None and _total_layers > 0:
|
|
n = max(1, min(int(finetune_last_n_layers), _total_layers))
|
|
layers_to_transform = list(range(_total_layers - n, _total_layers))
|
|
|
|
# Get only allowed parameters for LoraConfig
|
|
local_variables = {
|
|
**locals(),
|
|
**kwargs,
|
|
}
|
|
del local_variables["kwargs"]
|
|
allowed_parameters = inspect.signature(LoraConfig).parameters.keys()
|
|
lora_config = LoraConfig(
|
|
**{k: v for k, v in local_variables.items() if k in allowed_parameters},
|
|
)
|
|
model = prepare_model_for_kbit_training(
|
|
model,
|
|
use_gradient_checkpointing = use_gradient_checkpointing,
|
|
)
|
|
# Gemma4 ClippableLinear wraps nn.Linear -- PEFT can't inject LoRA on it directly.
|
|
# Monkey-patch PEFT to target the inner .linear child instead.
|
|
_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
|
|
|
|
model = _get_peft_model(model, lora_config)
|
|
|
|
# Restore original PEFT method
|
|
if _clippable_linear_cls is not None:
|
|
_LoraModel._create_and_replace = _original_car
|
|
# Apply QAT + LoRA if specified
|
|
if qat_scheme is not None:
|
|
print("Unsloth: Applying QAT to mitigate quantization degradation")
|
|
model = _prepare_model_for_qat(model, qat_scheme)
|
|
# Fix LoraConfig.auto_mapping is None
|
|
fix_lora_auto_mapping(model)
|
|
# Enable gradients on modules which are trainable
|
|
requires_grad_for_gradient_checkpointing(model)
|
|
trust_remote_code = getattr(model, "_unsloth_trust_remote_code", False)
|
|
model = FastBaseModel.post_patch_model(
|
|
model,
|
|
use_gradient_checkpointing = use_gradient_checkpointing,
|
|
trust_remote_code = trust_remote_code,
|
|
)
|
|
model.max_seq_length = max_seq_length
|
|
# Save to modules as well
|
|
for module in model.modules():
|
|
module.max_seq_length = max_seq_length
|
|
# Clear deleted GPU items
|
|
for _ in range(3):
|
|
gc.collect()
|
|
if DEVICE_TYPE in ("cuda", "hip"):
|
|
torch.cuda.empty_cache()
|
|
elif DEVICE_TYPE == "xpu":
|
|
torch.xpu.empty_cache()
|
|
patch_saving_functions(model, vision = True)
|
|
patch_peft_fast_inference(model)
|
|
|
|
# Add for_inference and for_training
|
|
model.for_training = functools.partial(FastBaseModel.for_training, model)
|
|
model.for_inference = functools.partial(FastBaseModel.for_inference, model)
|
|
m = model
|
|
while hasattr(m, "model"):
|
|
m.for_training = functools.partial(FastBaseModel.for_training, m)
|
|
m.for_inference = functools.partial(FastBaseModel.for_inference, m)
|
|
m = m.model
|
|
# Detect a stray pre-train forward so train() can drop the torch.compile
|
|
# graph cache it would otherwise poison (see prepare_for_training_mode).
|
|
_unsloth_install_pretrain_detector(model)
|
|
model = _exclude_rope_inv_freq_from_ddp(model)
|
|
return model
|
|
|
|
@staticmethod
|
|
def post_patch_model(
|
|
model,
|
|
use_gradient_checkpointing = True,
|
|
trust_remote_code = False,
|
|
model_type = None,
|
|
tokenizer = None,
|
|
float32_mixed_precision = None,
|
|
):
|
|
full_finetuning = os.environ.get("UNSLOTH_ENABLE_FULL_FINETUNING", "0") == "1"
|
|
|
|
if type(float32_mixed_precision) is bool:
|
|
# Respect whatever it was set before
|
|
pass
|
|
else:
|
|
float32_mixed_precision = True
|
|
if _get_dtype(dtype_from_config(model.config)) == torch.bfloat16 and full_finetuning:
|
|
# Use bfloat16 precision for full finetuning
|
|
float32_mixed_precision = False
|
|
|
|
# VLMs can hit DDP "marked ready twice" with re-entrant checkpointing.
|
|
# See: https://github.com/unslothai/unsloth/issues/3713.
|
|
use_reentrant = not is_distributed()
|
|
if not use_reentrant:
|
|
# Under DDP, avoid the offloaded/re-entrant checkpoint patch.
|
|
unpatch_unsloth_gradient_checkpointing()
|
|
unpatch_unsloth_smart_gradient_checkpointing()
|
|
# Force native checkpoint to default to non-reentrant for downstream calls.
|
|
_orig_checkpoint = torch_checkpoint.checkpoint
|
|
|
|
def _nonre_checkpoint(function, *args, **kwargs):
|
|
kwargs["use_reentrant"] = False
|
|
return _orig_checkpoint(function, *args, **kwargs)
|
|
|
|
torch_checkpoint.checkpoint = _nonre_checkpoint
|
|
hf_modeling_utils.checkpoint = _nonre_checkpoint
|
|
|
|
model = prepare_model_for_training(
|
|
model,
|
|
use_gradient_checkpointing = use_gradient_checkpointing,
|
|
use_reentrant = use_reentrant,
|
|
full_finetuning = full_finetuning,
|
|
train_layernorms = full_finetuning,
|
|
train_embedding = full_finetuning,
|
|
train_lm_head = full_finetuning,
|
|
float32_mixed_precision = float32_mixed_precision,
|
|
patch_modules_to_save = True,
|
|
)
|
|
# Persist the configured GC mode so the trainer restores it verbatim.
|
|
# for_inference() clears the module flags (GRPO does this every generation
|
|
# step), and a plain TrainingArguments defaults gradient_checkpointing=False,
|
|
# which would otherwise silently disable this setting at train time (#4735).
|
|
model._unsloth_gradient_checkpointing = use_gradient_checkpointing
|
|
|
|
# Gemma3N audio conformer processes variable-length audio tensors
|
|
# that cause stride mismatches in AOT autograd compiled backward
|
|
# when non-reentrant checkpointing is used. The notebook or TRL
|
|
# may override gradient_checkpointing_kwargs with use_reentrant=False
|
|
# after this point, so we intercept gradient_checkpointing_enable
|
|
# to always force use_reentrant=True for Gemma3N.
|
|
_model_type = getattr(getattr(model, "config", None), "model_type", "") or ""
|
|
if "gemma3n" in _model_type.lower() or "gemma4" in _model_type.lower():
|
|
_original_gc_enable = model.gradient_checkpointing_enable
|
|
|
|
def _gc_enable_reentrant(**kwargs):
|
|
gc_kwargs = kwargs.get("gradient_checkpointing_kwargs", {}) or {}
|
|
gc_kwargs["use_reentrant"] = True
|
|
kwargs["gradient_checkpointing_kwargs"] = gc_kwargs
|
|
return _original_gc_enable(**kwargs)
|
|
|
|
model.gradient_checkpointing_enable = _gc_enable_reentrant
|
|
|
|
from transformers.trainer import Trainer
|
|
|
|
if (
|
|
Trainer._inner_training_loop.__name__ != "_fast_inner_training_loop"
|
|
and trust_remote_code == False
|
|
):
|
|
raise RuntimeError("Unsloth: Unsuccessfully patched inner_training_loop")
|
|
patch_saving_functions(model, vision = True)
|
|
|
|
# Patch tokenizer to pad to the left
|
|
m = model
|
|
while hasattr(m, "model"):
|
|
if hasattr(m, "_saved_temp_tokenizer"):
|
|
if hasattr(m._saved_temp_tokenizer, "tokenizer"):
|
|
m._saved_temp_tokenizer.tokenizer.padding_side = "left"
|
|
m = m.model
|
|
if hasattr(m, "_saved_temp_tokenizer"):
|
|
if hasattr(m._saved_temp_tokenizer, "tokenizer"):
|
|
m._saved_temp_tokenizer.tokenizer.padding_side = "left"
|
|
# Prevent Transformers Trainer from auto-wrapping Unsloth LoRA models in DP.
|
|
_mark_unsloth_disable_data_parallel(model, disable = not full_finetuning)
|
|
|
|
# Clear deleted GPU items
|
|
for _ in range(3):
|
|
gc.collect()
|
|
if DEVICE_TYPE in ("cuda", "hip"):
|
|
torch.cuda.empty_cache()
|
|
elif DEVICE_TYPE == "xpu":
|
|
torch.xpu.empty_cache()
|
|
# Add for_inference and for_training
|
|
model.for_training = functools.partial(FastBaseModel.for_training, model)
|
|
model.for_inference = functools.partial(FastBaseModel.for_inference, model)
|
|
m = model
|
|
while hasattr(m, "model"):
|
|
m.for_training = functools.partial(FastBaseModel.for_training, m)
|
|
m.for_inference = functools.partial(FastBaseModel.for_inference, m)
|
|
m = m.model
|
|
# Set weight[padding_idx] = 0 for embeddings that are NOT tied with the
|
|
# lm_head. When weights are tied, zeroing the padding row also zeros
|
|
# the corresponding lm_head row, forcing logit = 0 for the pad token.
|
|
# Only do this if tokenizer is defined since eos_token == pad_token sometimes!
|
|
pad_token_id = getattr(tokenizer, "pad_token_id", None)
|
|
lm_head = getattr(model, "lm_head", None)
|
|
lm_head_weight = getattr(lm_head, "weight", None) if lm_head is not None else None
|
|
if tokenizer is not None and getattr(tokenizer, "eos_token_id", None) != pad_token_id:
|
|
with torch.no_grad():
|
|
for name, module in model.named_modules():
|
|
if type(module) is torch.nn.Embedding:
|
|
if (
|
|
getattr(module, "weight", None) is not None
|
|
and getattr(module, "padding_idx", None) is not None
|
|
):
|
|
if (
|
|
module.padding_idx == pad_token_id
|
|
and module.padding_idx < module.weight.shape[0]
|
|
):
|
|
# Skip if tied to lm_head
|
|
if (
|
|
lm_head_weight is not None
|
|
and module.weight.data_ptr() == lm_head_weight.data_ptr()
|
|
):
|
|
continue
|
|
module.weight[module.padding_idx] = 0
|
|
return model
|
|
|
|
@staticmethod
|
|
def for_inference(model):
|
|
if not hasattr(model, "parameters"):
|
|
raise TypeError(
|
|
"Unsloth: I think you're passing a tokenizer, not the model to for_inference!"
|
|
)
|
|
|
|
def _for_inference(m):
|
|
if hasattr(m, "gradient_checkpointing"):
|
|
m.gradient_checkpointing = False
|
|
if hasattr(m, "training"):
|
|
m.training = False
|
|
# Pad tokenizer to the left
|
|
if hasattr(m, "_saved_temp_tokenizer"):
|
|
m._saved_temp_tokenizer.padding_side = "left"
|
|
# Set a flag for generation!
|
|
m._flag_for_generation = True
|
|
|
|
m = model
|
|
while hasattr(m, "model"):
|
|
_for_inference(m)
|
|
m = m.model
|
|
_for_inference(m)
|
|
model.eval() # to turn off training on modules deeper in
|
|
|
|
# Since transformers 4.53, must turn off explicitly
|
|
for module in model.modules():
|
|
if hasattr(module, "gradient_checkpointing"):
|
|
module.gradient_checkpointing = False
|
|
|
|
# Also disable training for embeddings for NEFTune
|
|
if hasattr(model, "get_input_embeddings"):
|
|
embeddings = model.get_input_embeddings()
|
|
if hasattr(embeddings, "training"):
|
|
embeddings.training = False
|
|
if hasattr(model, "get_output_embeddings"):
|
|
embeddings = model.get_output_embeddings()
|
|
if hasattr(embeddings, "training"):
|
|
embeddings.training = False
|
|
# Restore use_cache values that prepare_model_for_training disabled
|
|
# for gradient checkpointing (older unsloth_zoo has no restore helper)
|
|
try:
|
|
from unsloth_zoo.training_utils import restore_use_cache
|
|
restore_use_cache(model)
|
|
except ImportError:
|
|
pass
|
|
|
|
# Must disable returning hidden states in the case for GRPO
|
|
os.environ["UNSLOTH_RETURN_HIDDEN_STATES"] = "0"
|
|
# Must enable returning logits
|
|
os.environ["UNSLOTH_RETURN_LOGITS"] = "1"
|
|
# Turn off skip guards and set stance to default
|
|
if torch_compiler_set_stance is not None:
|
|
torch_compiler_set_stance(stance = "default", skip_guard_eval_unsafe = False)
|
|
return model
|
|
|
|
@staticmethod
|
|
def for_training(model, use_gradient_checkpointing = True):
|
|
if not hasattr(model, "parameters"):
|
|
raise TypeError(
|
|
"Unsloth: I think you're passing a tokenizer, not the model to for_training!"
|
|
)
|
|
|
|
# Delete all fast inference loras
|
|
for param in model.parameters():
|
|
if hasattr(param, "_fast_lora"):
|
|
del param._fast_lora
|
|
|
|
def _for_training(m):
|
|
if hasattr(m, "gradient_checkpointing"):
|
|
m.gradient_checkpointing = use_gradient_checkpointing
|
|
if hasattr(m, "training"):
|
|
m.training = True
|
|
# Pad tokenizer to the left
|
|
if hasattr(m, "_saved_temp_tokenizer"):
|
|
m._saved_temp_tokenizer.padding_side = "right"
|
|
# Set a flag for generation!
|
|
if hasattr(m, "_flag_for_generation"):
|
|
try:
|
|
# Weirdly sometimes cannot succeed so do a try except
|
|
del m._flag_for_generation
|
|
except:
|
|
pass
|
|
|
|
m = model
|
|
while hasattr(m, "model"):
|
|
_for_training(m)
|
|
m = m.model
|
|
_for_training(m)
|
|
model.train() # to turn on training on modules deeper in
|
|
|
|
# Since transformers 4.53, must turn on explicitly
|
|
for module in model.modules():
|
|
if hasattr(module, "gradient_checkpointing"):
|
|
module.gradient_checkpointing = use_gradient_checkpointing
|
|
|
|
# Also re-enable training for embeddings for NEFTune
|
|
if hasattr(model, "get_input_embeddings"):
|
|
embeddings = model.get_input_embeddings()
|
|
if hasattr(embeddings, "training"):
|
|
embeddings.training = True
|
|
if hasattr(model, "get_output_embeddings"):
|
|
embeddings = model.get_output_embeddings()
|
|
if hasattr(embeddings, "training"):
|
|
embeddings.training = True
|
|
# Re-disable use_cache if prepare_model_for_training had disabled it
|
|
# and for_inference restored it (record only exists after a disable)
|
|
if (
|
|
use_gradient_checkpointing
|
|
and getattr(model, "_unsloth_use_cache_originals", None) is not None
|
|
):
|
|
try:
|
|
from unsloth_zoo.training_utils import disable_use_cache
|
|
disable_use_cache(model)
|
|
except ImportError:
|
|
pass
|
|
|
|
# Can re-enable not returning logits
|
|
os.environ["UNSLOTH_RETURN_LOGITS"] = "0"
|
|
# Turn off skip guards and set stance to default
|
|
if torch_compiler_set_stance is not None:
|
|
torch_compiler_set_stance(stance = "default", skip_guard_eval_unsafe = False)
|
|
return model
|
|
|
|
|
|
def _looks_like_message_list(value):
|
|
return isinstance(value, list) and (len(value) == 0 or isinstance(value[0], dict))
|
|
|
|
|
|
def _iter_message_lists(example, column):
|
|
if _looks_like_message_list(example):
|
|
yield example
|
|
return
|
|
if not isinstance(example, dict):
|
|
return
|
|
seen_keys = set()
|
|
for key in (column, "messages", "conversations", "prompt", "completion"):
|
|
if key in seen_keys:
|
|
continue
|
|
seen_keys.add(key)
|
|
value = example.get(key)
|
|
if _looks_like_message_list(value):
|
|
yield value
|
|
|
|
|
|
def _local_path_from_video_value(video_path):
|
|
# data: URIs are inline payloads, not files, and contain no "://"
|
|
if video_path.startswith("data:"):
|
|
return None
|
|
if "://" not in video_path:
|
|
return video_path
|
|
if not video_path.startswith("file://"):
|
|
return None
|
|
from urllib.parse import urlparse
|
|
from urllib.request import url2pathname
|
|
|
|
parsed = urlparse(video_path)
|
|
# RFC 8089: only an empty authority or "localhost" is the local machine
|
|
if parsed.netloc and parsed.netloc != "localhost":
|
|
return None
|
|
path = url2pathname(parsed.path)
|
|
return path or None
|
|
|
|
|
|
def check_dataset_for_missing_videos(
|
|
dataset,
|
|
column = "messages",
|
|
raise_error = True,
|
|
checked = None,
|
|
):
|
|
"""
|
|
Validate that local video paths referenced in a dataset exist, catching
|
|
missing files before training (torchvision otherwise returns an empty
|
|
tensor and the model silently receives no video signal).
|
|
|
|
Args:
|
|
dataset: Map-style Dataset, list of dicts, or iterable of examples
|
|
(not a streaming IterableDataset - iterating consumes it).
|
|
column: Chat-messages column, default "messages"; "conversations",
|
|
"prompt" and "completion" are also scanned.
|
|
raise_error: True (default) raises FileNotFoundError listing missing
|
|
files; False warns and returns them.
|
|
checked: Optional set of known-good paths for cross-call dedup.
|
|
|
|
Returns:
|
|
List[str]: Missing file paths (empty when all exist).
|
|
"""
|
|
try:
|
|
from datasets import IterableDataset as _IterableDataset
|
|
if isinstance(dataset, _IterableDataset):
|
|
warnings.warn(
|
|
"Unsloth: check_dataset_for_missing_videos received a streaming "
|
|
"IterableDataset; iterating would exhaust it and training would "
|
|
"see zero samples. Skipping validation - pass a map-style Dataset "
|
|
"or rely on the UnslothVisionDataCollator's per-batch check.",
|
|
stacklevel = 2,
|
|
)
|
|
return []
|
|
except ImportError:
|
|
pass
|
|
|
|
missing = []
|
|
# Report each missing path once; only confirmed-existing paths enter
|
|
# `checked`, so retries after an error re-check previously missing files.
|
|
seen_missing = set()
|
|
if checked is None:
|
|
checked = set()
|
|
|
|
for example in dataset:
|
|
for messages in _iter_message_lists(example, column):
|
|
for msg in messages:
|
|
if not isinstance(msg, dict):
|
|
continue
|
|
content = msg.get("content", [])
|
|
if not isinstance(content, (list, tuple)):
|
|
continue
|
|
for item in content:
|
|
if not isinstance(item, dict) or item.get("type") != "video":
|
|
continue
|
|
video_path = item.get("video", "")
|
|
if not isinstance(video_path, str) or not video_path:
|
|
continue
|
|
path = _local_path_from_video_value(video_path)
|
|
if path is None or path in checked or path in seen_missing:
|
|
continue
|
|
if not os.path.isfile(path):
|
|
seen_missing.add(path)
|
|
missing.append(path)
|
|
else:
|
|
checked.add(path)
|
|
|
|
if missing:
|
|
missing_list = "\n".join(f" - {p}" for p in missing)
|
|
error_msg = (
|
|
f"Unsloth: {len(missing)} video file(s) referenced in your dataset could not be found.\n"
|
|
"Training would silently continue with empty video tensors - the model would receive\n"
|
|
"no actual video signal while loss still appears to decrease.\n\n"
|
|
f"Missing files:\n{missing_list}\n\n"
|
|
"Fix: verify the video file paths in your dataset before calling the trainer."
|
|
)
|
|
if raise_error:
|
|
raise FileNotFoundError(error_msg)
|
|
warnings.warn(error_msg, stacklevel = 2)
|
|
|
|
return missing
|
|
|
|
|
|
# Auto-enable grouped-GEMM MoE (transformers<5 ModuleList experts); see llama.py.
|
|
try:
|
|
from unsloth_zoo.temporary_patches.moe_grouped_modulelist import wrap_loader_for_grouped_moe
|
|
FastBaseModel.from_pretrained = staticmethod(
|
|
wrap_loader_for_grouped_moe(FastBaseModel.from_pretrained)
|
|
)
|
|
FastBaseModel.get_peft_model = staticmethod(
|
|
wrap_loader_for_grouped_moe(FastBaseModel.get_peft_model)
|
|
)
|
|
except Exception:
|
|
pass
|