1818import json
1919import re
2020from pathlib import Path
21- from typing import Any
21+ from typing import Any , Callable
2222
2323import torch
2424import torch .nn .functional as F
@@ -52,40 +52,37 @@ def _normalize_text(text: str) -> str:
5252
5353
5454def _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
7881def _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-
8986def _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
163160class 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 ,)
0 commit comments