cover fixed

This commit is contained in:
RyanOnTheInside
2026-02-06 05:45:16 -05:00
parent c9776ac9de
commit 951b1318f8
3 changed files with 269 additions and 30 deletions
+78 -13
View File
@@ -228,32 +228,49 @@ class ACEStep15NativeCoverGuider(comfy.samplers.CFGGuider):
"""
ACE-Step 1.5 cover guider for style transfer/regeneration.
Uses the source audio as context (src_latents) while generating new content
across the entire duration. This is useful for cover songs, style transfer,
or regenerating audio with a new prompt while maintaining the original structure.
Uses semantic tokens (lm_hints) from the source audio as structural guidance
while generating new content with a different style/timbre.
Official behavior:
- chunk_masks: All 1s (generate everything)
- src_latents: Original audio latents (provides structural guidance)
- is_covers: 1 (use semantic hints instead of raw latents)
- precomputed_lm_hints_25Hz: Semantic representation of source audio
IMPORTANT: For proper cover behavior, provide semantic_hints from ACEStep15SemanticExtractor.
Without semantic_hints, falls back to using raw VAE latents (less accurate style transfer).
"""
def __init__(self, model, positive, negative, cfg, source_latent):
def __init__(self, model, positive, negative, cfg, source_latent, semantic_hints=None):
super().__init__(model)
self.set_conds(positive, negative)
self.set_cfg(cfg)
self.source_latent = source_latent
self.semantic_hints = semantic_hints
batch_size, channels, total_length = source_latent.shape
print(f"[ACE15_NATIVE_COVER] Initializing")
print(f"[ACE15_NATIVE_COVER] source_latent.shape: {source_latent.shape}")
print(f"[ACE15_NATIVE_COVER] source_latent stats: mean={source_latent.mean():.4f}, std={source_latent.std():.4f}")
if semantic_hints is not None:
print(f"[ACE15_NATIVE_COVER] semantic_hints.shape: {semantic_hints.shape}")
print(f"[ACE15_NATIVE_COVER] semantic_hints stats: mean={semantic_hints.mean():.4f}, std={semantic_hints.std():.4f}, min={semantic_hints.min():.4f}, max={semantic_hints.max():.4f}")
# Validate semantic hints have reasonable variance
if semantic_hints.std() < 0.01:
print(f"[ACE15_NATIVE_COVER] WARNING: semantic_hints have very low variance ({semantic_hints.std():.6f}) - may be constant/invalid!")
# Validate shape matches source
if semantic_hints.shape != source_latent.shape:
print(f"[ACE15_NATIVE_COVER] WARNING: semantic_hints shape {semantic_hints.shape} != source_latent shape {source_latent.shape}")
print(f"[ACE15_NATIVE_COVER] Using semantic hints for proper cover behavior (is_covers=1)")
else:
print(f"[ACE15_NATIVE_COVER] WARNING: No semantic_hints provided, falling back to VAE latents (is_covers=0)")
# Cover: generate everything but use source audio as context
# Cover: generate everything
# chunk_masks: All 1s (generate all frames)
self.chunk_masks = torch.ones_like(source_latent)
# src_latents: Original audio (provides structural guidance)
# src_latents: Original audio (used as fallback if is_covers=0)
self.src_latents = source_latent.clone()
print(f"[ACE15_NATIVE_COVER] chunk_masks.shape: {self.chunk_masks.shape}")
@@ -262,12 +279,13 @@ class ACEStep15NativeCoverGuider(comfy.samplers.CFGGuider):
self._wrapper_applied = False
def _apply_model_wrapper(self):
"""Apply model wrapper to inject chunk_masks and src_latents"""
"""Apply model wrapper to inject chunk_masks, src_latents, is_covers, and semantic hints"""
if self._wrapper_applied:
return
chunk_masks = self.chunk_masks
src_latents = self.src_latents
semantic_hints = self.semantic_hints
self.model_patcher.set_model_unet_function_wrapper(None)
@@ -286,11 +304,36 @@ class ACEStep15NativeCoverGuider(comfy.samplers.CFGGuider):
c["chunk_masks"] = cm
c["src_latents"] = sl
# Set is_covers based on whether we have semantic hints
if semantic_hints is not None:
# Proper cover: use semantic hints via is_covers=1
sh = semantic_hints.to(device=device, dtype=dtype)
if sh.shape[0] < input_batch_size:
sh = sh.repeat(input_batch_size, 1, 1)
c["precomputed_lm_hints_25Hz"] = sh
c["is_covers"] = torch.ones((input_batch_size,), device=device, dtype=torch.long)
else:
# Fallback: use raw VAE latents via is_covers=0
c["is_covers"] = torch.zeros((input_batch_size,), device=device, dtype=torch.long)
if not hasattr(model_function_wrapper, '_logged'):
print(f"[ACE15_COVER_WRAPPER] Injecting chunk_masks and src_latents")
print(f"[ACE15_COVER_WRAPPER] Injecting cover parameters")
print(f"[ACE15_COVER_WRAPPER] input.shape: {args['input'].shape}")
print(f"[ACE15_COVER_WRAPPER] input stats: mean={args['input'].mean():.4f}, std={args['input'].std():.4f}")
print(f"[ACE15_COVER_WRAPPER] chunk_masks.shape: {c['chunk_masks'].shape}")
print(f"[ACE15_COVER_WRAPPER] chunk_masks sum: {c['chunk_masks'].sum().item()} (should be all 1s for cover)")
print(f"[ACE15_COVER_WRAPPER] src_latents.shape: {c['src_latents'].shape}")
print(f"[ACE15_COVER_WRAPPER] src_latents stats: mean={c['src_latents'].mean():.4f}, std={c['src_latents'].std():.4f}")
print(f"[ACE15_COVER_WRAPPER] is_covers: {c['is_covers']} (1=use semantic hints, 0=use VAE latents)")
if "precomputed_lm_hints_25Hz" in c:
sh = c["precomputed_lm_hints_25Hz"]
print(f"[ACE15_COVER_WRAPPER] precomputed_lm_hints_25Hz.shape: {sh.shape}")
print(f"[ACE15_COVER_WRAPPER] precomputed_lm_hints_25Hz stats: mean={sh.mean():.4f}, std={sh.std():.4f}, min={sh.min():.4f}, max={sh.max():.4f}")
# Check if hints look valid (should have some variance, not constant)
if sh.std() < 0.01:
print(f"[ACE15_COVER_WRAPPER] WARNING: semantic hints have very low variance! May be constant/invalid.")
else:
print(f"[ACE15_COVER_WRAPPER] WARNING: No precomputed_lm_hints_25Hz - will fall back to VAE latents!")
model_function_wrapper._logged = True
return apply_model(args["input"], args["timestep"], **c)
@@ -338,23 +381,31 @@ class ACEStep15NativeExtractGuider(comfy.samplers.CFGGuider):
Official behavior:
- chunk_masks: All 1s (generate everything)
- src_latents: Original audio latents
- is_covers: 1 (use semantic hints for structure)
- precomputed_lm_hints_25Hz: Semantic representation of source audio
- Instruction: "Extract the {TRACK_NAME} track from the audio:"
IMPORTANT: For proper extract behavior, provide semantic_hints from ACEStep15SemanticExtractor.
"""
def __init__(self, model, positive, negative, cfg, source_latent, track_name="vocals"):
def __init__(self, model, positive, negative, cfg, source_latent, track_name="vocals", semantic_hints=None):
super().__init__(model)
self.set_conds(positive, negative)
self.set_cfg(cfg)
self.source_latent = source_latent
self.track_name = track_name
self.semantic_hints = semantic_hints
batch_size, channels, total_length = source_latent.shape
print(f"[ACE15_NATIVE_EXTRACT] Initializing")
print(f"[ACE15_NATIVE_EXTRACT] source_latent.shape: {source_latent.shape}")
print(f"[ACE15_NATIVE_EXTRACT] track_name: {track_name}")
if semantic_hints is not None:
print(f"[ACE15_NATIVE_EXTRACT] semantic_hints.shape: {semantic_hints.shape}")
else:
print(f"[ACE15_NATIVE_EXTRACT] WARNING: No semantic_hints provided, falling back to VAE latents")
# Extract: generate everything (the extracted track)
self.chunk_masks = torch.ones_like(source_latent)
@@ -365,12 +416,13 @@ class ACEStep15NativeExtractGuider(comfy.samplers.CFGGuider):
self._wrapper_applied = False
def _apply_model_wrapper(self):
"""Apply model wrapper to inject chunk_masks and src_latents"""
"""Apply model wrapper to inject chunk_masks, src_latents, is_covers, and semantic hints"""
if self._wrapper_applied:
return
chunk_masks = self.chunk_masks
src_latents = self.src_latents
semantic_hints = self.semantic_hints
self.model_patcher.set_model_unet_function_wrapper(None)
@@ -389,8 +441,21 @@ class ACEStep15NativeExtractGuider(comfy.samplers.CFGGuider):
c["chunk_masks"] = cm
c["src_latents"] = sl
# Set is_covers based on whether we have semantic hints
if semantic_hints is not None:
sh = semantic_hints.to(device=device, dtype=dtype)
if sh.shape[0] < input_batch_size:
sh = sh.repeat(input_batch_size, 1, 1)
c["precomputed_lm_hints_25Hz"] = sh
c["is_covers"] = torch.ones((input_batch_size,), device=device, dtype=torch.long)
else:
c["is_covers"] = torch.zeros((input_batch_size,), device=device, dtype=torch.long)
if not hasattr(model_function_wrapper, '_logged'):
print(f"[ACE15_EXTRACT_WRAPPER] Injecting chunk_masks and src_latents")
print(f"[ACE15_EXTRACT_WRAPPER] Injecting extract parameters")
print(f"[ACE15_EXTRACT_WRAPPER] is_covers: {c['is_covers']}")
if "precomputed_lm_hints_25Hz" in c:
print(f"[ACE15_EXTRACT_WRAPPER] precomputed_lm_hints_25Hz.shape: {c['precomputed_lm_hints_25Hz'].shape}")
model_function_wrapper._logged = True
return apply_model(args["input"], args["timestep"], **c)
+153 -6
View File
@@ -122,6 +122,117 @@ class ACEStep15SilenceLatentLoader:
return (silence_latent,)
class ACEStep15SemanticExtractor:
"""
Extracts semantic tokens from source audio latents for use with Cover/Extract tasks.
The ACE-Step 1.5 model uses semantic tokens (lm_hints) as structural guidance
for tasks like Cover and Extract. This node extracts those tokens from VAE latents.
Flow: VAE latents → tokenizer → indices → quantizer → detokenizer → semantic_hints
Use the output with ACE-Step 1.5 Cover Guider or Extract Guider for proper
style transfer behavior.
"""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"source_latents": ("LATENT",),
}
}
RETURN_TYPES = ("SEMANTIC_HINTS",)
RETURN_NAMES = ("semantic_hints",)
FUNCTION = "extract"
CATEGORY = "audio/acestep"
def extract(self, model, source_latents):
source_tensor = source_latents["samples"]
# Validate v1.5 shape: (batch, 64, length)
if len(source_tensor.shape) != 3 or source_tensor.shape[1] != 64:
raise ValueError(f"ACE-Step 1.5 requires latent shape (batch, 64, length), got {source_tensor.shape}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] Extracting semantic hints")
print(f"[ACE15_SEMANTIC_EXTRACTOR] source_tensor.shape: {source_tensor.shape}")
# Debug: Print model structure
print(f"[ACE15_SEMANTIC_EXTRACTOR] model type: {type(model)}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] model.__class__.__name__: {model.__class__.__name__}")
if hasattr(model, 'model'):
print(f"[ACE15_SEMANTIC_EXTRACTOR] model.model type: {type(model.model)}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] model.model.__class__.__name__: {model.model.__class__.__name__}")
if hasattr(model.model, 'diffusion_model'):
print(f"[ACE15_SEMANTIC_EXTRACTOR] model.model.diffusion_model type: {type(model.model.diffusion_model)}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] model.model.diffusion_model.__class__.__name__: {model.model.diffusion_model.__class__.__name__}")
# Get the diffusion model (AceStepConditionGenerationModel)
# It has .tokenizer and .detokenizer attributes
diffusion_model = model.model.diffusion_model
if not hasattr(diffusion_model, 'tokenizer') or not hasattr(diffusion_model, 'detokenizer'):
raise RuntimeError(
"Model does not have tokenizer/detokenizer. "
"Make sure you're using an ACE-Step 1.5 model."
)
# Load model to GPU using ComfyUI's model management
# Pass the ModelPatcher (model), not the raw model (model.model)
print(f"[ACE15_SEMANTIC_EXTRACTOR] Loading model to GPU...")
comfy.model_management.load_model_gpu(model)
device = comfy.model_management.get_torch_device()
dtype = model.model.get_dtype()
print(f"[ACE15_SEMANTIC_EXTRACTOR] device: {device}, dtype: {dtype}")
# Move source tensor to model device/dtype
# Source is in ComfyUI format: [B, D, T] = [B, 64, length]
# Tokenizer expects: [B, T, D] = [B, length, 64]
source_on_device = source_tensor.to(device=device, dtype=dtype)
source_transposed = source_on_device.movedim(-1, -2) # [B, T, D]
print(f"[ACE15_SEMANTIC_EXTRACTOR] source_transposed.shape: {source_transposed.shape}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] source stats: mean={source_transposed.mean():.4f}, std={source_transposed.std():.4f}, min={source_transposed.min():.4f}, max={source_transposed.max():.4f}")
# Verify tokenizer/detokenizer weights are on GPU
tokenizer_device = next(diffusion_model.tokenizer.parameters()).device
detokenizer_device = next(diffusion_model.detokenizer.parameters()).device
print(f"[ACE15_SEMANTIC_EXTRACTOR] tokenizer weights on: {tokenizer_device}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] detokenizer weights on: {detokenizer_device}")
# Step 1: Tokenize - get quantized embeddings at 5Hz
# tokenizer.tokenize() handles the window pooling internally
# Returns: quantized [B, T/5, hidden_size], indices [B, T/5, num_quantizers]
with torch.no_grad():
quantized, indices = diffusion_model.tokenizer.tokenize(source_transposed)
print(f"[ACE15_SEMANTIC_EXTRACTOR] quantized.shape: {quantized.shape}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] quantized stats: mean={quantized.mean():.4f}, std={quantized.std():.4f}, min={quantized.min():.4f}, max={quantized.max():.4f}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] indices.shape: {indices.shape}")
# Step 2: Detokenize - upsample from 5Hz to 25Hz
# FIX: Use quantized directly instead of going through get_output_from_indices
# This avoids potential precision loss from the redundant index lookup
with torch.no_grad():
lm_hints = diffusion_model.detokenizer(quantized)
print(f"[ACE15_SEMANTIC_EXTRACTOR] lm_hints.shape: {lm_hints.shape}")
print(f"[ACE15_SEMANTIC_EXTRACTOR] lm_hints stats: mean={lm_hints.mean():.4f}, std={lm_hints.std():.4f}, min={lm_hints.min():.4f}, max={lm_hints.max():.4f}")
# Transpose back to ComfyUI format: [B, T, D] → [B, D, T]
semantic_hints = lm_hints.movedim(-1, -2)
print(f"[ACE15_SEMANTIC_EXTRACTOR] semantic_hints.shape (output): {semantic_hints.shape}")
# Move to CPU for storage
semantic_hints = semantic_hints.cpu()
return (semantic_hints,)
class ACEStepRepaintGuiderNode:
"""Node that creates a repaint guider for use with SamplerCustomAdvanced"""
@@ -724,8 +835,11 @@ class ACEStep15NativeCoverGuiderNode:
"""
Creates an ACE-Step 1.5 cover guider for style transfer/regeneration.
Uses the source audio as context while generating new content across
the entire duration. Useful for cover songs or regenerating with a new prompt.
Uses semantic tokens from the source audio as structural guidance while
generating new content with a different style/timbre.
IMPORTANT: For proper cover behavior, connect semantic_hints from ACEStep15SemanticExtractor.
Without semantic_hints, falls back to using raw VAE latents (less accurate style transfer).
"""
@classmethod
@@ -737,6 +851,9 @@ class ACEStep15NativeCoverGuiderNode:
"negative": ("CONDITIONING",),
"cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
"source_latents": ("LATENT",),
},
"optional": {
"semantic_hints": ("SEMANTIC_HINTS",),
}
}
@@ -744,7 +861,7 @@ class ACEStep15NativeCoverGuiderNode:
FUNCTION = "get_guider"
CATEGORY = "sampling/custom_sampling/guiders"
def get_guider(self, model, positive, negative, cfg, source_latents):
def get_guider(self, model, positive, negative, cfg, source_latents, semantic_hints=None):
source_tensor = source_latents["samples"]
# Validate v1.5 shape: (batch, 64, length)
@@ -753,9 +870,13 @@ class ACEStep15NativeCoverGuiderNode:
print(f"[ACE15_COVER_NODE] Creating guider")
print(f"[ACE15_COVER_NODE] source_tensor.shape: {source_tensor.shape}")
if semantic_hints is not None:
print(f"[ACE15_COVER_NODE] semantic_hints.shape: {semantic_hints.shape}")
else:
print(f"[ACE15_COVER_NODE] WARNING: No semantic_hints provided - using fallback mode")
guider = ACEStep15NativeCoverGuider(
model, positive, negative, cfg, source_tensor
model, positive, negative, cfg, source_tensor, semantic_hints
)
return (guider,)
@@ -767,6 +888,8 @@ class ACEStep15NativeExtractGuiderNode:
Extracts a specific track (vocals, drums, bass, etc.) from the source audio.
Use with the ACEStep15TaskTextEncode node with task_type="extract".
IMPORTANT: For proper extract behavior, connect semantic_hints from ACEStep15SemanticExtractor.
"""
@classmethod
@@ -780,6 +903,9 @@ class ACEStep15NativeExtractGuiderNode:
"source_latents": ("LATENT",),
"track_name": (["vocals", "drums", "bass", "guitar", "keyboard", "strings",
"percussion", "synth", "fx", "brass", "woodwinds", "backing_vocals"],),
},
"optional": {
"semantic_hints": ("SEMANTIC_HINTS",),
}
}
@@ -787,7 +913,7 @@ class ACEStep15NativeExtractGuiderNode:
FUNCTION = "get_guider"
CATEGORY = "sampling/custom_sampling/guiders"
def get_guider(self, model, positive, negative, cfg, source_latents, track_name):
def get_guider(self, model, positive, negative, cfg, source_latents, track_name, semantic_hints=None):
source_tensor = source_latents["samples"]
# Validate v1.5 shape: (batch, 64, length)
@@ -797,9 +923,11 @@ class ACEStep15NativeExtractGuiderNode:
print(f"[ACE15_EXTRACT_NODE] Creating guider")
print(f"[ACE15_EXTRACT_NODE] source_tensor.shape: {source_tensor.shape}")
print(f"[ACE15_EXTRACT_NODE] track_name: {track_name}")
if semantic_hints is not None:
print(f"[ACE15_EXTRACT_NODE] semantic_hints.shape: {semantic_hints.shape}")
guider = ACEStep15NativeExtractGuider(
model, positive, negative, cfg, source_tensor, track_name
model, positive, negative, cfg, source_tensor, track_name, semantic_hints
)
return (guider,)
@@ -960,9 +1088,24 @@ class ACEStep15TaskTextEncodeNode:
print(f"[ACE15_TEXT_ENCODE] Encoding with task_type={task_type}, track_name={track_name or 'N/A'}")
# Import to get task instruction for verification
from .patches import get_task_instruction, is_patched
instruction = get_task_instruction(task_type, track_name if track_name else None)
print(f"[ACE15_TEXT_ENCODE] patches applied: {is_patched()}")
print(f"[ACE15_TEXT_ENCODE] task instruction: {instruction}")
# Use the patched tokenize_with_weights which accepts task_type
# encode_from_tokens_scheduled returns conditioning directly (list format)
tokens = clip.tokenize(text, **kwargs)
# Verify the tokens contain task_type info (patched tokenizer should include this)
if isinstance(tokens, dict):
if "task_type" in tokens:
print(f"[ACE15_TEXT_ENCODE] tokens contain task_type: {tokens.get('task_type')}")
else:
print(f"[ACE15_TEXT_ENCODE] WARNING: tokens dict does not contain task_type - patch may not be applied!")
print(f"[ACE15_TEXT_ENCODE] tokens keys: {list(tokens.keys())}")
conditioning = clip.encode_from_tokens_scheduled(tokens)
# For negative conditioning, use ComfyUI's ConditioningZeroOut node
@@ -984,6 +1127,8 @@ NODE_CLASS_MAPPINGS = {
"ACEStepAudioPostProcessor": ACEStepAudioPostProcessor,
# ACE-Step 1.5 loaders
"ACEStep15SilenceLatentLoader": ACEStep15SilenceLatentLoader,
# ACE-Step 1.5 semantic extraction
"ACEStep15SemanticExtractor": ACEStep15SemanticExtractor,
# ACE-Step 1.5 guiders (model-level mask input)
"ACEStep15NativeEditGuider": ACEStep15NativeEditGuiderNode,
"ACEStep15NativeCoverGuider": ACEStep15NativeCoverGuiderNode,
@@ -1007,6 +1152,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ACEStepAudioPostProcessor": "ACEStep Audio Post Processor",
# ACE-Step 1.5 loaders
"ACEStep15SilenceLatentLoader": "ACE-Step 1.5 Load Silence Latent",
# ACE-Step 1.5 semantic extraction
"ACEStep15SemanticExtractor": "ACE-Step 1.5 Semantic Extractor",
# ACE-Step 1.5 guiders
"ACEStep15NativeEditGuider": "ACE-Step 1.5 Edit Guider (Extend/Repaint)",
"ACEStep15NativeCoverGuider": "ACE-Step 1.5 Cover Guider",
+38 -11
View File
@@ -71,29 +71,46 @@ def _create_patched_ace_step15_forward(original_forward):
Create a patched forward method for AceStepConditionGenerationModel.
The patched version:
1. Accepts chunk_masks and src_latents as kwargs
2. Sets is_covers=0 when src_latents is provided (to use provided latents instead of lm_hints)
1. Accepts chunk_masks, src_latents, is_covers, and precomputed_lm_hints_25Hz as kwargs
2. Allows guiders to explicitly control is_covers flag for different task types
3. Properly transposes user-provided tensors from (batch, channels, length) to (batch, length, channels)
Task type behavior via is_covers:
- is_covers=0: Use provided src_latents directly (extend/repaint)
- is_covers=1: Use lm_hints from precomputed_lm_hints_25Hz or audio_codes (cover/extract)
- is_covers=None: Default behavior (src_latents = x)
This enables extend, repaint, cover, extract, and lego task types.
"""
# One-time logging flag
_forward_logged = [False]
@functools.wraps(original_forward)
def patched_forward(self, x, timestep, context, lyric_embed=None, refer_audio=None,
audio_codes=None, chunk_masks=None, src_latents=None, **kwargs):
audio_codes=None, chunk_masks=None, src_latents=None,
is_covers=None, precomputed_lm_hints_25Hz=None, **kwargs):
text_attention_mask = None
lyric_attention_mask = None
refer_audio_order_mask = None
attention_mask = None
# chunk_masks and src_latents now come from kwargs for extend/repaint support
# When src_latents is explicitly provided (for extend/repaint), we need is_covers=0
# so prepare_condition() uses our src_latents instead of replacing with lm_hints
is_covers = None
if src_latents is not None:
# Set is_covers to 0 to indicate we want to use the provided src_latents
# One-time diagnostic log for cover task
if not _forward_logged[0] and precomputed_lm_hints_25Hz is not None:
print(f"[ACE15_PATCHED_FORWARD] Cover mode detected")
print(f"[ACE15_PATCHED_FORWARD] x.shape (before transpose): {x.shape}")
print(f"[ACE15_PATCHED_FORWARD] is_covers: {is_covers}")
print(f"[ACE15_PATCHED_FORWARD] precomputed_lm_hints_25Hz.shape (before transpose): {precomputed_lm_hints_25Hz.shape}")
print(f"[ACE15_PATCHED_FORWARD] precomputed_lm_hints_25Hz stats: mean={precomputed_lm_hints_25Hz.mean():.4f}, std={precomputed_lm_hints_25Hz.std():.4f}")
_forward_logged[0] = True
# is_covers can now be passed explicitly by guiders:
# - Cover/Extract tasks: is_covers=1 (use lm_hints from precomputed_lm_hints_25Hz)
# - Extend/Repaint tasks: is_covers=0 (use provided src_latents)
# - Default (not provided): fallback to old behavior
if is_covers is None and src_latents is not None:
# Backwards compatibility: default to 0 when src_latents provided but is_covers not specified
is_covers = torch.zeros((x.shape[0],), device=x.device, dtype=torch.long)
precomputed_lm_hints_25Hz = None
lyric_hidden_states = lyric_embed
text_hidden_states = context
refer_audio_acoustic_hidden_states_packed = refer_audio.movedim(-1, -2)
@@ -103,11 +120,21 @@ def _create_patched_ace_step15_forward(original_forward):
if refer_audio_order_mask is None:
refer_audio_order_mask = torch.zeros((x.shape[0],), device=x.device, dtype=torch.long)
# Transpose user-provided src_latents/chunk_masks from (batch, channels, length) to (batch, length, channels)
# Transpose user-provided tensors from ComfyUI format (batch, channels, length)
# to model format (batch, length, channels)
if src_latents is not None:
src_latents = src_latents.movedim(-1, -2)
if chunk_masks is not None:
chunk_masks = chunk_masks.movedim(-1, -2)
if precomputed_lm_hints_25Hz is not None:
# precomputed_lm_hints_25Hz comes in ComfyUI format [B, D, T], transpose to [B, T, D]
precomputed_lm_hints_25Hz = precomputed_lm_hints_25Hz.movedim(-1, -2)
# Log post-transpose shape (one-time)
if _forward_logged[0] and not hasattr(patched_forward, '_post_transpose_logged'):
print(f"[ACE15_PATCHED_FORWARD] precomputed_lm_hints_25Hz.shape (after transpose): {precomputed_lm_hints_25Hz.shape}")
print(f"[ACE15_PATCHED_FORWARD] x.shape (after transpose): {x.shape}")
print(f"[ACE15_PATCHED_FORWARD] src_latents.shape (after transpose): {src_latents.shape if src_latents is not None else 'None'}")
patched_forward._post_transpose_logged = True
if src_latents is None and is_covers is None:
src_latents = x