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
339 lines
13 KiB
Python
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
|