Files
unslothai--unsloth/unsloth/models/diffusion.py
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

339 lines
13 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.
"""
FastDiffusionModel: a transformers-only slow path for text-diffusion models (e.g. DiffusionGemma).
These models use a block-diffusion sampling loop (custom generate) and a novel backbone, so we skip
Unsloth's autoregressive kernel/compile patching and load the unmodified HF model (outputs stay
bit-identical to transformers), keeping only the safe conveniences: 4bit/8bit loading, PEFT LoRA, the
(model, tokenizer) API, and for_inference/for_training. Extend DIFFUSION_MODEL_TYPES as more land.
"""
import os
import torch
from transformers import AutoConfig, AutoProcessor, AutoTokenizer
from ._utils import is_bfloat16_supported, maybe_prefetch_hf_snapshot
from .llama import logger
__all__ = ["FastDiffusionModel", "DIFFUSION_MODEL_TYPES", "is_diffusion_model_type"]
# transformers model_type strings routed to this slow path
DIFFUSION_MODEL_TYPES = ("diffusion_gemma", "diffusion_gemma4")
# Default LoRA targets: standard nn.Linear modules in the shared Gemma-4 backbone. The 128 MoE experts
# are fused 3D Parameters (gate_up_proj/down_proj), not nn.Linear, so PEFT LoRA cannot target them.
DIFFUSION_LORA_TARGETS = [
"q_proj",
"k_proj",
"v_proj",
"o_proj", # attention
"gate_proj",
"up_proj",
"down_proj", # dense (non-expert) MLP
]
# Vision tower uses a custom Linear with the same suffix names; exclude it so only the text path is wrapped.
DIFFUSION_LORA_EXCLUDE = r".*(vision_tower|embed_vision).*"
def is_diffusion_model_type(model_types):
"""model_types: str or iterable -> True if any is a known diffusion model_type."""
if isinstance(model_types, str):
model_types = (model_types,)
return any(mt in DIFFUSION_MODEL_TYPES for mt in model_types)
def _resolve_diffusion_model_class(config):
"""Resolve the HF model class for a diffusion checkpoint from config.architectures."""
import transformers
archs = getattr(config, "architectures", None) or []
for arch in archs:
cls = getattr(transformers, arch, None)
if cls is not None:
return cls
# Fallbacks across naming revisions.
for name in (
"DiffusionGemmaForBlockDiffusion",
"DiffusionGemma4ModelForBlockDiffusion",
"DiffusionGemma4ForBlockDiffusion",
):
cls = getattr(transformers, name, None)
if cls is not None:
return cls
raise RuntimeError(
f"Unsloth: could not resolve a diffusion model class from architectures={archs}. "
"Ensure you have the transformers build that ships the DiffusionGemma implementation."
)
def _load_diffusion_config(
model_name,
token,
trust_remote_code,
revision,
local_files_only,
cache_dir = None,
):
"""Load the config, aliasing the legacy ``diffusion_gemma`` model_type to the ``diffusion_gemma4``
classes current transformers ships. AutoConfig raises on the legacy type; catch that, rewrite the
type/arch names in-memory, and rebuild."""
try:
return AutoConfig.from_pretrained(
model_name,
token = token,
trust_remote_code = trust_remote_code,
revision = revision,
local_files_only = local_files_only,
cache_dir = cache_dir,
)
except ValueError as e:
if "diffusion_gemma" not in str(e):
raise
import json
from transformers.utils import cached_file
cfg_path = cached_file(
model_name,
"config.json",
token = token,
revision = revision,
local_files_only = local_files_only,
cache_dir = cache_dir,
)
with open(cfg_path, encoding = "utf-8") as f:
cd = json.load(f)
cd["model_type"] = "diffusion_gemma4"
cd.setdefault("architectures", ["DiffusionGemma4ModelForBlockDiffusion"])
if isinstance(cd.get("text_config"), dict):
cd["text_config"]["model_type"] = "diffusion_gemma4_text"
if isinstance(cd.get("vision_config"), dict):
cd["vision_config"]["model_type"] = "diffusion_gemma4_vision"
from transformers import DiffusionGemma4Config
return DiffusionGemma4Config.from_dict(cd)
class FastDiffusionModel:
"""transformers-only slow path for text-diffusion models."""
@staticmethod
def from_pretrained(
model_name = "google/diffusiongemma-26B-A4B-it",
max_seq_length = None, # API-compat; diffusion uses canvas_length
dtype = None,
load_in_4bit = False,
load_in_8bit = False,
load_in_16bit = False,
full_finetuning = False,
token = None,
device_map = "auto",
trust_remote_code = False,
attn_implementation = "eager", # exact match with the reference golden logits
revision = None,
return_tokenizer = True,
**kwargs,
):
SUPPORTS_BFLOAT16 = is_bfloat16_supported()
if dtype is None:
dtype = torch.float16 if not SUPPORTS_BFLOAT16 else torch.bfloat16
elif dtype == torch.bfloat16 and not SUPPORTS_BFLOAT16:
logger.warning_once("Device does not support bfloat16. Will change to float16.")
dtype = torch.float16
assert dtype in (torch.float16, torch.bfloat16, torch.float32)
# Honor an explicit local_files_only; else fall back to the offline env vars.
local_files_only = kwargs.pop("local_files_only", None)
if local_files_only is None:
local_files_only = (
os.environ.get("HF_HUB_OFFLINE", "0") == "1"
or os.environ.get("TRANSFORMERS_OFFLINE", "0") == "1"
)
cache_dir = kwargs.get("cache_dir")
config = _load_diffusion_config(
model_name,
token,
trust_remote_code,
revision,
local_files_only,
cache_dir = cache_dir,
)
model_type = getattr(config, "model_type", None)
if not is_diffusion_model_type(model_type):
raise RuntimeError(
f"Unsloth: FastDiffusionModel only supports diffusion model_types {DIFFUSION_MODEL_TYPES}, "
f"got '{model_type}'. Use FastModel/FastLanguageModel for autoregressive models."
)
model_cls = _resolve_diffusion_model_class(config)
# Prefetch the whole repo root so the weight load is a cache hit. No subfolder: the pipeline
# loads every component subfolder, so narrowing would leave unet/vae/text_encoder to Xet.
maybe_prefetch_hf_snapshot(
model_name,
token = token,
revision = revision,
cache_dir = cache_dir,
local_files_only = local_files_only,
fast_inference = False,
force_download = kwargs.get("force_download", False),
use_safetensors = kwargs.get("use_safetensors"),
# Forward variant (e.g. "fp16") so the warm keeps variant weights.
variant = kwargs.get("variant"),
)
load_kwargs = dict(
dtype = dtype,
device_map = device_map,
token = token,
trust_remote_code = trust_remote_code,
attn_implementation = attn_implementation,
revision = revision,
local_files_only = local_files_only,
cache_dir = cache_dir,
)
# Match the load's weight format to the warm (None/auto already matches).
if kwargs.get("use_safetensors") is not None:
load_kwargs["use_safetensors"] = kwargs["use_safetensors"]
# Forward variant to the real load so it reads the warmed variant weights.
if kwargs.get("variant") is not None:
load_kwargs["variant"] = kwargs["variant"]
# Optional bitsandbytes quant. The MoE experts (3D Parameters) are not nn.Linear so bnb skips
# them; only attention + dense MLP Linears quantize, lm_head/embeddings stay full precision.
if load_in_4bit or load_in_8bit:
from transformers import BitsAndBytesConfig
if load_in_4bit:
qcfg = BitsAndBytesConfig(
load_in_4bit = True,
bnb_4bit_use_double_quant = True,
bnb_4bit_quant_type = "nf4",
bnb_4bit_compute_dtype = dtype,
llm_int8_skip_modules = [
"lm_head",
"embed_tokens",
"experts",
"self_conditioning",
"router",
],
)
else:
qcfg = BitsAndBytesConfig(load_in_8bit = True)
load_kwargs["quantization_config"] = qcfg
print(f"==(( Unsloth: FastDiffusionModel (slow / transformers-only path) ))==")
print(f" Model: {model_name} | class: {model_cls.__name__} | model_type: {model_type}")
print(
f" dtype: {dtype} | 4bit: {load_in_4bit} | 8bit: {load_in_8bit} | attn: {attn_implementation}"
)
model = model_cls.from_pretrained(model_name, **load_kwargs).eval()
# Mark before any early return so get_peft_model/for_* route to the slow path.
model._unsloth_slow_diffusion = True
if not return_tokenizer:
return model, None
# Prefer the processor (chat template + tokenizer); fall back to a bare tokenizer. Returned as
# "tokenizer" to match the Unsloth (model, tokenizer) contract.
try:
tokenizer = AutoProcessor.from_pretrained(
model_name,
token = token,
trust_remote_code = trust_remote_code,
revision = revision,
local_files_only = local_files_only,
cache_dir = cache_dir,
)
except Exception:
tokenizer = AutoTokenizer.from_pretrained(
model_name,
token = token,
trust_remote_code = trust_remote_code,
revision = revision,
local_files_only = local_files_only,
cache_dir = cache_dir,
)
return model, tokenizer
@staticmethod
def get_peft_model(
model,
r = 16,
target_modules = None,
lora_alpha = 16,
lora_dropout = 0.0,
bias = "none",
use_gradient_checkpointing = True,
random_state = 3407,
task_type = None,
**kwargs,
):
"""Attach a PEFT LoRA to the diffusion backbone (attention + dense MLP). No fused kernels."""
from peft import LoraConfig, get_peft_model as peft_get_peft_model
if target_modules is None:
target_modules = DIFFUSION_LORA_TARGETS
lora_kwargs = dict(
r = r,
lora_alpha = lora_alpha,
lora_dropout = lora_dropout,
bias = bias,
target_modules = target_modules,
task_type = task_type, # None: diffusion has no standard CAUSAL_LM head
**{k: v for k, v in kwargs.items() if k in ("modules_to_save", "init_lora_weights")},
)
# Exclude the vision tower's custom (non-Linear) modules that share suffix names.
exclude = kwargs.get("exclude_modules", DIFFUSION_LORA_EXCLUDE)
try:
lora_config = LoraConfig(exclude_modules = exclude, **lora_kwargs)
except TypeError:
# Older PEFT without exclude_modules: scope the target to the text decoder by regex.
lora_kwargs["target_modules"] = (
r".*model\.decoder\.layers\.\d+\.(self_attn\.[qkvo]_proj|mlp\.(gate|up|down)_proj)"
)
lora_config = LoraConfig(**lora_kwargs)
if use_gradient_checkpointing:
model.gradient_checkpointing_enable()
if hasattr(model, "enable_input_require_grads"):
model.enable_input_require_grads()
model = peft_get_peft_model(model, lora_config)
model._unsloth_slow_diffusion = True
try:
model.print_trainable_parameters()
except Exception:
pass
return model
@staticmethod
def for_inference(model):
model.eval()
for _, m in model.named_modules():
if hasattr(m, "gradient_checkpointing"):
m.gradient_checkpointing = False
return model
@staticmethod
def for_training(model, use_gradient_checkpointing = True):
model.train()
if use_gradient_checkpointing and hasattr(model, "gradient_checkpointing_enable"):
model.gradient_checkpointing_enable()
return model