Skip to content

Commit 9a05478

Browse files
committed
upd
Signed-off-by: Lancer <maruixiang6688@gmail.com>
1 parent ee820bd commit 9a05478

5 files changed

Lines changed: 121 additions & 142 deletions

File tree

src/diffusers/models/autoencoders/autoencoder_longcat_audio_dit.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from ...configuration_utils import ConfigMixin, register_to_config
2727
from ...utils import BaseOutput
28+
from ...utils.accelerate_utils import apply_forward_hook
2829
from ...utils.torch_utils import randn_tensor
2930
from ..modeling_utils import ModelMixin
3031
from .vae import AutoencoderMixin
@@ -293,6 +294,8 @@ class LongCatAudioDiTVaeDecoderOutput(BaseOutput):
293294

294295

295296
class LongCatAudioDiTVae(ModelMixin, AutoencoderMixin, ConfigMixin):
297+
_supports_group_offloading = False
298+
296299
@register_to_config
297300
def __init__(
298301
self,
@@ -342,6 +345,7 @@ def __init__(
342345
upsample_shortcut=upsample_shortcut,
343346
)
344347

348+
@apply_forward_hook
345349
def encode(
346350
self,
347351
sample: torch.Tensor,
@@ -367,6 +371,7 @@ def encode(
367371
return (latents,)
368372
return LongCatAudioDiTVaeEncoderOutput(latents=latents)
369373

374+
@apply_forward_hook
370375
def decode(
371376
self, latents: torch.Tensor, return_dict: bool = True
372377
) -> LongCatAudioDiTVaeDecoderOutput | tuple[torch.Tensor]:

src/diffusers/models/transformers/transformer_longcat_audio_dit.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ def forward(
454454

455455
class LongCatAudioDiTTransformer(ModelMixin, ConfigMixin):
456456
_supports_gradient_checkpointing = False
457+
_repeated_blocks = ["AudioDiTBlock"]
457458

458459
@register_to_config
459460
def __init__(
@@ -543,8 +544,7 @@ def forward(
543544
latent_cond: torch.Tensor | None = None,
544545
return_dict: bool = True,
545546
) -> LongCatAudioDiTTransformerOutput | tuple[torch.Tensor]:
546-
dtype = next(self.parameters()).dtype
547-
hidden_states = hidden_states.to(dtype)
547+
dtype = hidden_states.dtype
548548
encoder_hidden_states = encoder_hidden_states.to(dtype)
549549
timestep = timestep.to(dtype)
550550
batch_size = hidden_states.shape[0]
@@ -558,7 +558,7 @@ def forward(
558558
encoder_hidden_states = encoder_hidden_states.masked_fill(text_mask.logical_not().unsqueeze(-1), 0.0)
559559
hidden_states = self.input_embed(hidden_states, attention_mask)
560560
if self.use_latent_condition and latent_cond is not None:
561-
latent_cond = self.latent_embed(latent_cond.to(dtype), attention_mask)
561+
latent_cond = self.latent_embed(latent_cond.to(hidden_states.dtype), attention_mask)
562562
hidden_states = self.latent_cond_embedder(torch.cat([hidden_states, latent_cond], dim=-1))
563563
residual = hidden_states.clone() if self.config.long_skip else None
564564
rope = self.rotary_embed(hidden_states, hidden_states.shape[1])

src/diffusers/pipelines/longcat_audio_dit/pipeline_longcat_audio_dit.py

Lines changed: 91 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import json
1919
import re
2020
from pathlib import Path
21-
from typing import Any
21+
from typing import Any, Callable
2222

2323
import torch
2424
import torch.nn.functional as F
@@ -52,40 +52,37 @@ def _normalize_text(text: str) -> str:
5252

5353

5454
def _approx_duration_from_text(text: str | list[str], max_duration: float = 30.0) -> float:
55-
if isinstance(text, list):
56-
if not text:
57-
return 0.0
58-
return max(_approx_duration_from_text(prompt, max_duration=max_duration) for prompt in text)
55+
if not text:
56+
return 0.0
57+
if isinstance(text, str):
58+
text = [text]
5959

6060
en_dur_per_char = 0.082
6161
zh_dur_per_char = 0.21
62-
text = re.sub(r"\s+", "", text)
63-
num_zh = num_en = num_other = 0
64-
for char in text:
65-
if "一" <= char <= "鿿":
66-
num_zh += 1
67-
elif char.isalpha():
68-
num_en += 1
62+
durations = []
63+
for prompt in text:
64+
prompt = re.sub(r"\s+", "", prompt)
65+
num_zh = num_en = num_other = 0
66+
for char in prompt:
67+
if "一" <= char <= "鿿":
68+
num_zh += 1
69+
elif char.isalpha():
70+
num_en += 1
71+
else:
72+
num_other += 1
73+
if num_zh > num_en:
74+
num_zh += num_other
6975
else:
70-
num_other += 1
71-
if num_zh > num_en:
72-
num_zh += num_other
73-
else:
74-
num_en += num_other
75-
return min(max_duration, num_zh * zh_dur_per_char + num_en * en_dur_per_char)
76+
num_en += num_other
77+
durations.append(num_zh * zh_dur_per_char + num_en * en_dur_per_char)
78+
return min(max_duration, max(durations)) if durations else 0.0
7679

7780

7881
def _extract_prefixed_state_dict(state_dict: dict[str, torch.Tensor], prefix: str) -> dict[str, torch.Tensor]:
7982
prefix = f"{prefix}."
8083
return {key[len(prefix) :]: value for key, value in state_dict.items() if key.startswith(prefix)}
8184

8285

83-
def _get_uniform_flow_match_scheduler_sigmas(num_inference_steps: int) -> list[float]:
84-
num_inference_steps = max(int(num_inference_steps), 2)
85-
num_updates = num_inference_steps - 1
86-
return torch.linspace(1.0, 1.0 / num_updates, num_updates, dtype=torch.float32).tolist()
87-
88-
8986
def _load_longcat_tokenizer(
9087
pretrained_model_name_or_path: str | Path,
9188
text_encoder_model: str | None,
@@ -162,6 +159,7 @@ def _resolve_longcat_file(
162159

163160
class LongCatAudioDiTPipeline(DiffusionPipeline):
164161
model_cpu_offload_seq = "text_encoder->transformer->vae"
162+
_callback_tensor_inputs = ["latents", "prompt_embeds"]
165163

166164
def __init__(
167165
self,
@@ -188,6 +186,14 @@ def __init__(
188186
self.text_norm_feat = True
189187
self.text_add_embed = True
190188

189+
@property
190+
def guidance_scale(self):
191+
return self._guidance_scale
192+
193+
@property
194+
def num_timesteps(self):
195+
return self._num_timesteps
196+
191197
@classmethod
192198
@validate_hf_hub_args
193199
def from_pretrained(
@@ -371,7 +377,7 @@ def encode_prompt(self, prompt: str | list[str], device: torch.device) -> tuple[
371377
first_hidden = F.layer_norm(first_hidden, (first_hidden.shape[-1],), eps=1e-6)
372378
prompt_embeds = prompt_embeds + first_hidden
373379
lengths = attention_mask.sum(dim=1).to(device)
374-
return prompt_embeds.float(), lengths
380+
return prompt_embeds, lengths
375381

376382
def prepare_latents(
377383
self,
@@ -405,13 +411,22 @@ def check_inputs(
405411
prompt: list[str],
406412
negative_prompt: str | list[str] | None,
407413
output_type: str,
414+
callback_on_step_end_tensor_inputs: list[str] | None = None,
408415
) -> None:
409416
if len(prompt) == 0:
410417
raise ValueError("`prompt` must contain at least one prompt.")
411418

412419
if output_type not in {"np", "pt", "latent"}:
413420
raise ValueError(f"Unsupported output_type: {output_type}")
414421

422+
if callback_on_step_end_tensor_inputs is not None and not all(
423+
k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
424+
):
425+
raise ValueError(
426+
f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found "
427+
f"{[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
428+
)
429+
415430
if negative_prompt is not None and not isinstance(negative_prompt, str):
416431
negative_prompt = list(negative_prompt)
417432
if len(negative_prompt) != len(prompt):
@@ -431,6 +446,8 @@ def __call__(
431446
generator: torch.Generator | list[torch.Generator] | None = None,
432447
output_type: str = "np",
433448
return_dict: bool = True,
449+
callback_on_step_end: Callable[[int, int], None] | None = None,
450+
callback_on_step_end_tensor_inputs: list[str] = ["latents"],
434451
):
435452
r"""
436453
Function invoked when calling the pipeline for generation.
@@ -442,20 +459,26 @@ def __call__(
442459
Target audio duration in seconds. Ignored when `latents` is provided.
443460
latents (`torch.Tensor`, *optional*):
444461
Pre-generated noisy latents of shape `(batch_size, duration, latent_dim)`.
445-
num_inference_steps (`int`, defaults to 16): Number of denoising steps. Values below 2 are promoted to 2.
462+
num_inference_steps (`int`, defaults to 16): Number of denoising steps.
446463
guidance_scale (`float`, defaults to 4.0): Guidance scale for classifier-free guidance.
447464
generator (`torch.Generator` or `list[torch.Generator]`, *optional*): Random generator(s).
448465
output_type (`str`, defaults to `"np"`): Output format: `"np"`, `"pt"`, or `"latent"`.
449466
return_dict (`bool`, defaults to `True`): Whether to return `AudioPipelineOutput`.
467+
callback_on_step_end (`Callable`, *optional*):
468+
A function called at the end of each denoising step with the pipeline, step index, timestep, and tensor
469+
inputs specified by `callback_on_step_end_tensor_inputs`.
470+
callback_on_step_end_tensor_inputs (`list`, defaults to `["latents"]`):
471+
Tensor inputs passed to `callback_on_step_end`.
450472
"""
451473
if prompt is None:
452474
prompt = []
453475
elif isinstance(prompt, str):
454476
prompt = [prompt]
455477
else:
456478
prompt = list(prompt)
457-
self.check_inputs(prompt, negative_prompt, output_type)
479+
self.check_inputs(prompt, negative_prompt, output_type, callback_on_step_end_tensor_inputs)
458480
batch_size = len(prompt)
481+
self._guidance_scale = guidance_scale
459482

460483
device = self._execution_device
461484
normalized_prompts = [_normalize_text(text) for text in prompt]
@@ -469,69 +492,77 @@ def __call__(
469492
if latents is None:
470493
duration = max(1, min(duration, max_duration))
471494

472-
text_condition, text_condition_len = self.encode_prompt(normalized_prompts, device)
495+
prompt_embeds, prompt_embeds_len = self.encode_prompt(normalized_prompts, device)
473496
duration_tensor = torch.full((batch_size,), duration, device=device, dtype=torch.long)
474497
mask = _lens_to_mask(duration_tensor)
475-
text_mask = _lens_to_mask(text_condition_len, length=text_condition.shape[1])
498+
text_mask = _lens_to_mask(prompt_embeds_len, length=prompt_embeds.shape[1])
476499

477500
if negative_prompt is None:
478-
neg_text = torch.zeros_like(text_condition)
479-
neg_text_len = text_condition_len
480-
neg_text_mask = text_mask
501+
negative_prompt_embeds = torch.zeros_like(prompt_embeds)
502+
negative_prompt_embeds_len = prompt_embeds_len
503+
negative_prompt_embeds_mask = text_mask
481504
else:
482505
if isinstance(negative_prompt, str):
483506
negative_prompt = [negative_prompt] * batch_size
484507
else:
485508
negative_prompt = list(negative_prompt)
486-
neg_text, neg_text_len = self.encode_prompt(negative_prompt, device)
487-
neg_text_mask = _lens_to_mask(neg_text_len, length=neg_text.shape[1])
509+
negative_prompt_embeds, negative_prompt_embeds_len = self.encode_prompt(negative_prompt, device)
510+
negative_prompt_embeds_mask = _lens_to_mask(
511+
negative_prompt_embeds_len, length=negative_prompt_embeds.shape[1]
512+
)
488513

489-
latent_cond = torch.zeros(batch_size, duration, self.latent_dim, device=device, dtype=text_condition.dtype)
514+
latent_cond = torch.zeros(batch_size, duration, self.latent_dim, device=device, dtype=prompt_embeds.dtype)
490515
latents = self.prepare_latents(
491-
batch_size, duration, device, text_condition.dtype, generator=generator, latents=latents
516+
batch_size, duration, device, prompt_embeds.dtype, generator=generator, latents=latents
492517
)
493-
if num_inference_steps < 2:
494-
logger.warning("`num_inference_steps`=%s is not supported; using 2 instead.", num_inference_steps)
495-
num_inference_steps = 2
518+
if num_inference_steps < 1:
519+
raise ValueError("num_inference_steps must be a positive integer.")
496520

497-
self.scheduler.set_timesteps(
498-
sigmas=_get_uniform_flow_match_scheduler_sigmas(num_inference_steps),
499-
device=device,
500-
)
521+
sigmas = torch.linspace(1.0, 1.0 / num_inference_steps, num_inference_steps, dtype=torch.float32).tolist()
522+
self.scheduler.set_timesteps(sigmas=sigmas, device=device)
501523
self.scheduler.set_begin_index(0)
502524
timesteps = self.scheduler.timesteps
503-
sample = latents
525+
self._num_timesteps = len(timesteps)
504526

505-
for t in timesteps:
506-
curr_t = (t / self.scheduler.config.num_train_timesteps).expand(batch_size).to(dtype=text_condition.dtype)
527+
for i, t in enumerate(timesteps):
528+
curr_t = (t / self.scheduler.config.num_train_timesteps).expand(batch_size).to(dtype=prompt_embeds.dtype)
507529
pred = self.transformer(
508-
hidden_states=sample,
509-
encoder_hidden_states=text_condition,
530+
hidden_states=latents,
531+
encoder_hidden_states=prompt_embeds,
510532
encoder_attention_mask=text_mask,
511533
timestep=curr_t,
512534
attention_mask=mask,
513535
latent_cond=latent_cond,
514536
).sample
515-
if guidance_scale > 1.0:
537+
if self.guidance_scale > 1.0:
516538
null_pred = self.transformer(
517-
hidden_states=sample,
518-
encoder_hidden_states=neg_text,
519-
encoder_attention_mask=neg_text_mask,
539+
hidden_states=latents,
540+
encoder_hidden_states=negative_prompt_embeds,
541+
encoder_attention_mask=negative_prompt_embeds_mask,
520542
timestep=curr_t,
521543
attention_mask=mask,
522544
latent_cond=latent_cond,
523545
).sample
524-
pred = null_pred + (pred - null_pred) * guidance_scale
525-
sample = self.scheduler.step(pred, t, sample, return_dict=False)[0]
546+
pred = null_pred + (pred - null_pred) * self.guidance_scale
547+
latents = self.scheduler.step(pred, t, latents, return_dict=False)[0]
548+
549+
if callback_on_step_end is not None:
550+
callback_kwargs = {}
551+
for k in callback_on_step_end_tensor_inputs:
552+
callback_kwargs[k] = locals()[k]
553+
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
554+
555+
latents = callback_outputs.pop("latents", latents)
556+
prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
526557

527558
if output_type == "latent":
528-
if not return_dict:
529-
return (sample,)
530-
return AudioPipelineOutput(audios=sample)
559+
waveform = latents
560+
else:
561+
waveform = self.vae.decode(latents.permute(0, 2, 1)).sample
562+
if output_type == "np":
563+
waveform = waveform.cpu().float().numpy()
531564

532-
waveform = self.vae.decode(sample.permute(0, 2, 1)).sample
533-
if output_type == "np":
534-
waveform = waveform.cpu().float().numpy()
565+
self.maybe_free_model_hooks()
535566

536567
if not return_dict:
537568
return (waveform,)

tests/models/transformers/test_models_transformer_longcat_audio_dit.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,14 @@ class TestLongCatAudioDiTTransformer(LongCatAudioDiTTransformerTesterConfig, Mod
8787

8888
class TestLongCatAudioDiTTransformerMemory(LongCatAudioDiTTransformerTesterConfig, MemoryTesterMixin):
8989
def test_layerwise_casting_memory(self):
90-
pytest.skip("LongCatAudioDiTTransformer does not support standard layerwise casting memory tests yet.")
91-
92-
def test_layerwise_casting_training(self):
93-
pytest.skip("LongCatAudioDiTTransformer does not support standard layerwise casting training tests yet.")
94-
95-
def test_group_offloading_with_layerwise_casting(self, *args, **kwargs):
9690
pytest.skip(
97-
"LongCatAudioDiTTransformer does not support combined group offloading and layerwise casting tests yet."
91+
"LongCatAudioDiTTransformer tiny test config does not provide stable layerwise casting peak memory "
92+
"coverage."
9893
)
9994

10095

10196
class TestLongCatAudioDiTTransformerCompile(LongCatAudioDiTTransformerTesterConfig, TorchCompileTesterMixin):
102-
def test_torch_compile_repeated_blocks(self):
103-
pytest.skip("LongCatAudioDiTTransformer does not define repeated blocks for regional compilation.")
97+
pass
10498

10599

106100
class TestLongCatAudioDiTTransformerAttention(LongCatAudioDiTTransformerTesterConfig, AttentionTesterMixin):

0 commit comments

Comments
 (0)