Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/diffusers/loaders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def text_encoder_attn_modules(text_encoder):
if is_transformers_available():
_import_structure["single_file"] = ["FromSingleFileMixin"]
_import_structure["lora_pipeline"] = [
"AceStepLoraLoaderMixin",
"AmusedLoraLoaderMixin",
"AnimaLoraLoaderMixin",
"StableDiffusionLoraLoaderMixin",
Expand Down Expand Up @@ -118,6 +119,7 @@ def text_encoder_attn_modules(text_encoder):
SD3IPAdapterMixin,
)
from .lora_pipeline import (
AceStepLoraLoaderMixin,
AmusedLoraLoaderMixin,
AnimaLoraLoaderMixin,
AuraFlowLoraLoaderMixin,
Expand Down
28 changes: 28 additions & 0 deletions src/diffusers/loaders/lora_conversion_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3094,3 +3094,31 @@ def convert_module(module):
raise ValueError(f"`state_dict` should be empty at this point but has {state_dict.keys()=}")

return converted_state_dict


def _convert_non_diffusers_ace_step_lora_to_diffusers(state_dict):
"""Convert an ACE-Step-1.5 (PEFT format) LoRA state dict to diffusers key names.

The original ACE-Step repo targets ``q_proj``, ``k_proj``, ``v_proj``, ``o_proj`` on the DiT decoder while
diffusers renames them to ``to_q``, ``to_k``, ``to_v``, ``to_out.0``. Keys arrive as
``base_model.model.layers.{i}.{self_attn|cross_attn}.{proj}.lora_{A|B}.weight`` and are mapped to
``transformer.layers.{i}.{self_attn|cross_attn}.{proj_diffusers}.lora_{A|B}.weight``.
"""
_PROJ_RENAMES = {
".q_proj.": ".to_q.",
".k_proj.": ".to_k.",
".v_proj.": ".to_v.",
".o_proj.": ".to_out.0.",
}

converted_state_dict = {}
for key in list(state_dict.keys()):
new_key = key
if new_key.startswith("base_model.model."):
new_key = new_key[len("base_model.model.") :]
for old, new in _PROJ_RENAMES.items():
new_key = new_key.replace(old, new)
new_key = f"transformer.{new_key}"
converted_state_dict[new_key] = state_dict.pop(key)

return converted_state_dict
203 changes: 203 additions & 0 deletions src/diffusers/loaders/lora_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
_convert_kohya_flux2_lora_to_diffusers,
_convert_kohya_flux_lora_to_diffusers,
_convert_musubi_wan_lora_to_diffusers,
_convert_non_diffusers_ace_step_lora_to_diffusers,
_convert_non_diffusers_anima_lora_to_diffusers,
_convert_non_diffusers_flux2_lora_to_diffusers,
_convert_non_diffusers_hidream_lora_to_diffusers,
Expand Down Expand Up @@ -6844,6 +6845,208 @@ def unfuse_lora(self, components: list[str] = ["transformer"], **kwargs):
super().unfuse_lora(components=components, **kwargs)


class AceStepLoraLoaderMixin(LoraBaseMixin):
r"""
Load LoRA layers into [`AceStepTransformer1DModel`]. Specific to [`AceStepPipeline`].
"""

_lora_loadable_modules = ["transformer"]
transformer_name = TRANSFORMER_NAME

@classmethod
@validate_hf_hub_args
def lora_state_dict(
cls,
pretrained_model_name_or_path_or_dict: str | dict[str, torch.Tensor],
**kwargs,
):
r"""
See [`~loaders.StableDiffusionLoraLoaderMixin.lora_state_dict`] for more details.
"""
cache_dir = kwargs.pop("cache_dir", None)
force_download = kwargs.pop("force_download", False)
proxies = kwargs.pop("proxies", None)
local_files_only = kwargs.pop("local_files_only", None)
token = kwargs.pop("token", None)
revision = kwargs.pop("revision", None)
subfolder = kwargs.pop("subfolder", None)
weight_name = kwargs.pop("weight_name", None)
use_safetensors = kwargs.pop("use_safetensors", None)
return_lora_metadata = kwargs.pop("return_lora_metadata", False)

allow_pickle = False
if use_safetensors is None:
use_safetensors = True
allow_pickle = True

user_agent = {"file_type": "attn_procs_weights", "framework": "pytorch"}

state_dict, metadata = _fetch_state_dict(
pretrained_model_name_or_path_or_dict=pretrained_model_name_or_path_or_dict,
weight_name=weight_name,
use_safetensors=use_safetensors,
local_files_only=local_files_only,
cache_dir=cache_dir,
force_download=force_download,
proxies=proxies,
token=token,
revision=revision,
subfolder=subfolder,
user_agent=user_agent,
allow_pickle=allow_pickle,
)

is_dora_scale_present = any("dora_scale" in k for k in state_dict)
if is_dora_scale_present:
warn_msg = "It seems like you are using a DoRA checkpoint that is not compatible in Diffusers at the moment. So, we are going to filter out the keys associated to 'dora_scale` from the state dict. If you think this is a mistake please open an issue https://github.com/huggingface/diffusers/issues/new."
logger.warning(warn_msg)
state_dict = {k: v for k, v in state_dict.items() if "dora_scale" not in k}

# Detect original ACE-Step-1.5 PEFT format (q_proj/k_proj naming).
is_original_ace_step = any("q_proj" in k or "k_proj" in k for k in state_dict)
if is_original_ace_step:
state_dict = _convert_non_diffusers_ace_step_lora_to_diffusers(state_dict)

out = (state_dict, metadata) if return_lora_metadata else state_dict
return out

# Copied from diffusers.loaders.lora_pipeline.CogVideoXLoraLoaderMixin.load_lora_weights
def load_lora_weights(
self,
pretrained_model_name_or_path_or_dict: str | dict[str, torch.Tensor],
adapter_name: str | None = None,
hotswap: bool = False,
**kwargs,
):
"""
See [`~loaders.StableDiffusionLoraLoaderMixin.load_lora_weights`] for more details.
"""
if not USE_PEFT_BACKEND:
raise ValueError("PEFT backend is required for this method.")

low_cpu_mem_usage = kwargs.pop("low_cpu_mem_usage", _LOW_CPU_MEM_USAGE_DEFAULT_LORA)
if low_cpu_mem_usage and is_peft_version("<", "0.13.0"):
raise ValueError(
"`low_cpu_mem_usage=True` is not compatible with this `peft` version. Please update it with `pip install -U peft`."
)

# if a dict is passed, copy it instead of modifying it inplace
if isinstance(pretrained_model_name_or_path_or_dict, dict):
pretrained_model_name_or_path_or_dict = pretrained_model_name_or_path_or_dict.copy()

# First, ensure that the checkpoint is a compatible one and can be successfully loaded.
kwargs["return_lora_metadata"] = True
state_dict, metadata = self.lora_state_dict(pretrained_model_name_or_path_or_dict, **kwargs)

is_correct_format = all("lora" in key for key in state_dict.keys())
if not is_correct_format:
raise ValueError("Invalid LoRA checkpoint. Make sure all LoRA param names contain `'lora'` substring.")

self.load_lora_into_transformer(
state_dict,
transformer=getattr(self, self.transformer_name) if not hasattr(self, "transformer") else self.transformer,
adapter_name=adapter_name,
metadata=metadata,
_pipeline=self,
low_cpu_mem_usage=low_cpu_mem_usage,
hotswap=hotswap,
)

@classmethod
# Copied from diffusers.loaders.lora_pipeline.SD3LoraLoaderMixin.load_lora_into_transformer with SD3Transformer2DModel->AceStepTransformer1DModel
def load_lora_into_transformer(
cls,
state_dict,
transformer,
adapter_name=None,
_pipeline=None,
low_cpu_mem_usage=False,
hotswap: bool = False,
metadata=None,
):
"""
See [`~loaders.StableDiffusionLoraLoaderMixin.load_lora_into_unet`] for more details.
"""
if low_cpu_mem_usage and is_peft_version("<", "0.13.0"):
raise ValueError(
"`low_cpu_mem_usage=True` is not compatible with this `peft` version. Please update it with `pip install -U peft`."
)

# Load the layers corresponding to transformer.
logger.info(f"Loading {cls.transformer_name}.")
transformer.load_lora_adapter(
state_dict,
network_alphas=None,
adapter_name=adapter_name,
metadata=metadata,
_pipeline=_pipeline,
low_cpu_mem_usage=low_cpu_mem_usage,
hotswap=hotswap,
)

@classmethod
# Copied from diffusers.loaders.lora_pipeline.CogVideoXLoraLoaderMixin.save_lora_weights
def save_lora_weights(
cls,
save_directory: str | os.PathLike,
transformer_lora_layers: dict[str, torch.nn.Module | torch.Tensor] = None,
is_main_process: bool = True,
weight_name: str = None,
save_function: Callable = None,
safe_serialization: bool = True,
transformer_lora_adapter_metadata: dict | None = None,
):
r"""
See [`~loaders.StableDiffusionLoraLoaderMixin.save_lora_weights`] for more information.
"""
lora_layers = {}
lora_metadata = {}

if transformer_lora_layers:
lora_layers[cls.transformer_name] = transformer_lora_layers
lora_metadata[cls.transformer_name] = transformer_lora_adapter_metadata

if not lora_layers:
raise ValueError("You must pass at least one of `transformer_lora_layers` or `text_encoder_lora_layers`.")

cls._save_lora_weights(
save_directory=save_directory,
lora_layers=lora_layers,
lora_metadata=lora_metadata,
is_main_process=is_main_process,
weight_name=weight_name,
save_function=save_function,
safe_serialization=safe_serialization,
)

# Copied from diffusers.loaders.lora_pipeline.CogVideoXLoraLoaderMixin.fuse_lora
def fuse_lora(
self,
components: list[str] = ["transformer"],
lora_scale: float = 1.0,
safe_fusing: bool = False,
adapter_names: list[str] | None = None,
**kwargs,
):
r"""
See [`~loaders.StableDiffusionLoraLoaderMixin.fuse_lora`] for more details.
"""
super().fuse_lora(
components=components,
lora_scale=lora_scale,
safe_fusing=safe_fusing,
adapter_names=adapter_names,
**kwargs,
)

# Copied from diffusers.loaders.lora_pipeline.CogVideoXLoraLoaderMixin.unfuse_lora
def unfuse_lora(self, components: list[str] = ["transformer"], **kwargs):
r"""
See [`~loaders.StableDiffusionLoraLoaderMixin.unfuse_lora`] for more details.
"""
super().unfuse_lora(components=components, **kwargs)


class LoraLoaderMixin(StableDiffusionLoraLoaderMixin):
def __init__(self, *args, **kwargs):
deprecation_message = "LoraLoaderMixin is deprecated and this will be removed in a future version. Please use `StableDiffusionLoraLoaderMixin`, instead."
Expand Down
10 changes: 8 additions & 2 deletions src/diffusers/models/transformers/ace_step_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
import torch.nn.functional as F

from ...configuration_utils import ConfigMixin, register_to_config
from ...utils import logging
from ...loaders import PeftAdapterMixin
from ...utils import apply_lora_scale, logging
from ..attention import AttentionMixin, AttentionModuleMixin
from ..attention_dispatch import (
AttentionBackendName,
Expand Down Expand Up @@ -428,7 +429,7 @@ def forward(
# --------------------------------------------------------------------------- #


class AceStepTransformer1DModel(ModelMixin, ConfigMixin, AttentionMixin, CacheMixin):
class AceStepTransformer1DModel(ModelMixin, ConfigMixin, PeftAdapterMixin, AttentionMixin, CacheMixin):
"""Diffusion Transformer for ACE-Step 1.5 music generation.

Generates audio latents conditioned on text, lyrics, and timbre. Uses 1D patch embedding (`Conv1d` with stride
Expand Down Expand Up @@ -528,13 +529,15 @@ def __init__(

self.gradient_checkpointing = False

@apply_lora_scale("attention_kwargs")
def forward(
self,
hidden_states: torch.Tensor,
timestep: torch.Tensor,
timestep_r: torch.Tensor,
encoder_hidden_states: torch.Tensor,
context_latents: torch.Tensor,
attention_kwargs: Optional[dict] = None,
return_dict: bool = True,
) -> Union[torch.Tensor, Transformer2DModelOutput]:
"""The [`AceStepTransformer1DModel`] forward method.
Expand All @@ -551,6 +554,9 @@ def forward(
context_latents (`torch.Tensor` of shape `(batch_size, seq_len, context_dim)`):
Context latents (source latents concatenated with chunk masks) — fed to the patchify conv alongside
`hidden_states`.
attention_kwargs (`dict`, *optional*):
A kwargs dictionary passed along to the `AttentionProcessor`. Used to pass the LoRA scale via
`{"scale": float}`.
return_dict (`bool`, defaults to `True`):
Whether to return a `Transformer2DModelOutput` or a plain tuple.

Expand Down
15 changes: 14 additions & 1 deletion src/diffusers/pipelines/ace_step/pipeline_ace_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from transformers import PreTrainedModel, PreTrainedTokenizerFast

from ...guiders.adaptive_projected_guidance import MomentumBuffer, normalized_guidance
from ...loaders import AceStepLoraLoaderMixin
from ...models import AutoencoderOobleck
from ...models.transformers.ace_step_transformer import AceStepTransformer1DModel
from ...schedulers import FlowMatchEulerDiscreteScheduler
Expand Down Expand Up @@ -129,7 +130,7 @@ def _normalize_audio_codes(audio_codes: Union[str, List[str]], batch_size: int)
"""


class AceStepPipeline(DiffusionPipeline):
class AceStepPipeline(DiffusionPipeline, AceStepLoraLoaderMixin):
r"""
Pipeline for text-to-music generation using ACE-Step 1.5.

Expand Down Expand Up @@ -224,6 +225,10 @@ def guidance_scale(self) -> float:
def num_timesteps(self) -> int:
return self._num_timesteps

@property
def attention_kwargs(self):
return self._attention_kwargs

def check_inputs(
self,
prompt: Union[str, List[str]],
Expand Down Expand Up @@ -825,6 +830,7 @@ def __call__(
cfg_interval_start: float = 0.0,
cfg_interval_end: float = 1.0,
timesteps: Optional[List[float]] = None,
attention_kwargs: Optional[dict] = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring missing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring missing?

Fixed in 944e759

):
r"""
The call function to the pipeline for music generation.
Expand Down Expand Up @@ -908,6 +914,9 @@ def __call__(
End ratio (0.0-1.0) of the timestep range where CFG is applied.
timesteps (`List[float]`, *optional*):
Custom timestep schedule. If provided, overrides `num_inference_steps` and `shift`.
attention_kwargs (`dict`, *optional*):
A kwargs dictionary passed along to the `AttentionProcessor`. Used to pass the LoRA scale via
`{"scale": float}`.

Examples:

Expand Down Expand Up @@ -983,6 +992,7 @@ def __call__(
# step-end callback can read them without the full arg bundle.
self._guidance_scale = guidance_scale
self._num_timesteps = num_inference_steps
self._attention_kwargs = attention_kwargs
self._interrupt = False

# Auto-generate instruction based on task_type if not provided
Expand Down Expand Up @@ -1176,6 +1186,7 @@ def __call__(
timestep_r=torch.cat([t_curr_tensor, t_curr_tensor], dim=0),
encoder_hidden_states=torch.cat([encoder_hidden_states, null_encoder_hidden_states], dim=0),
context_latents=torch.cat([context_latents, context_latents], dim=0),
attention_kwargs=self.attention_kwargs,
return_dict=False,
)
vt_cond, vt_uncond = model_output[0].chunk(2, dim=0)
Expand All @@ -1199,6 +1210,7 @@ def __call__(
timestep_r=t_curr_tensor,
encoder_hidden_states=encoder_hidden_states,
context_latents=context_latents,
attention_kwargs=self.attention_kwargs,
return_dict=False,
)
vt = model_output[0]
Expand All @@ -1211,6 +1223,7 @@ def __call__(
timestep_r=t_curr_tensor,
encoder_hidden_states=non_cover_encoder_hidden_states,
context_latents=context_latents,
attention_kwargs=self.attention_kwargs,
return_dict=False,
)
vt_nc = nc_output[0]
Expand Down
Loading
Loading