Merge 4ece4eebfc6c79c857f7c67d71b3a306c458ae43 into 440268d3940eb14a01595439bbc05c4aacde9c72

This commit is contained in:
Yousef R. Gamaleldin 2025-12-03 13:53:57 -08:00 committed by GitHub
commit 8ed3395adc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1257546 additions and 9 deletions

View File

@ -0,0 +1,734 @@
from __future__ import annotations
import math
import copy
import torch
import inspect
import warnings
from enum import Enum
from dataclasses import dataclass, fields
from typing import Optional, Union, Any
from abc import ABC, abstractmethod
import comfy.model_management
class CacheLayerParent(ABC):
def __init__(self):
self.keys, self.values = None, None
@abstractmethod
def lazy_init(self, key_states): ...
@abstractmethod
def update(self, key_states, value_states, cache_kwargs = None): ...
@abstractmethod
def get_seq_length(self) -> int: ...
@abstractmethod
def get_max_cache_shape(self) -> int: ...
def reset(self):
if self.keys is not None:
self.keys.zero_()
self.values.zero_()
class Cache:
def __init__(self):
self.layers = []
# TODO: offload logic
def get_seq_length(self, layer_idx = 0):
if layer_idx >= len(self.layers):
return 0
return self.layers[layer_idx].get_seq_length()
def get_max_cache_shape(self, layer_idx = 0):
if layer_idx >= len(self.layers): # for future dynamic cache impl.
return -1
return self.layers[layer_idx].get_max_cache_shape()
def reset(self):
for layer_idx in range(len(self.layers)):
self.layers[layer_idx].reset()
def update(self, key_states, value_states, layer_idx, cache_kwargs):
keys, values = self.layers[layer_idx].update(key_states, value_states, cache_kwargs)
return keys, values
@property
def max_cache_len(self):
values = [layer.max_cache_len for layer in self.layers]
return max(values)
class StaticLayer(CacheLayerParent):
def __init__(self, max_cache_len):
super().__init__()
self.max_cache_len = max_cache_len
def get_max_cache_shape(self):
return self.max_cache_len
def get_seq_length(self):
return (self.keys[0, 0].any(dim=-1)).sum() if self.keys is not None else 0
def lazy_init(self, key_states):
self.max_batch_size, self.num_heads, _, self.head_dim = key_states.shape
self.dtype, self.device = key_states.dtype, key_states.device
self.keys = torch.zeros(
(self.max_batch_size, self.num_heads, self.max_cache_len, self.head_dim),
dtype=self.dtype,
device=self.device,
)
self.values = torch.zeros(
(self.max_batch_size, self.num_heads, self.max_cache_len, self.head_dim),
dtype=self.dtype,
device=self.device,
)
def update(self, key_states, value_states, cache_kwargs = None):
if self.keys is None:
self.lazy_init(key_states)
cache_position = cache_kwargs.get("cache_position") if cache_kwargs is not None else None
cache_position = (
cache_position if cache_position is not None else torch.arange(key_states.shape[-2], device=self.device)
)
try:
self.keys.index_copy_(2, cache_position, key_states)
self.values.index_copy_(2, cache_position, value_states)
except NotImplementedError:
# mps fallback
self.keys[:, :, cache_position] = key_states
self.values[:, :, cache_position] = value_states
return self.keys, self.values
class StaticCache(Cache):
def __init__(self, config, max_cache_len):
self.layers = [StaticLayer(max_cache_len) for _ in range(config.num_hidden_layers)]
# TODO
class DynamicCache(Cache):
pass
NEED_SETUP_CACHE_CLASSES_MAPPING = { "static": StaticCache }
class TopKLogits:
def __init__(self, top_k: int, filter_value: float = -float("Inf"), min_tokens_to_keep: int = 1):
self.top_k = max(top_k, min_tokens_to_keep)
self.filter_value = filter_value
def __call__(self, scores: torch.FloatTensor) -> torch.FloatTensor:
top_k = min(self.top_k, scores.size(-1)) # cap top_k with vocab_size
indices_to_remove = scores < torch.topk(scores, top_k)[0][..., -1, None]
scores_processed = scores.masked_fill(indices_to_remove, self.filter_value)
return scores_processed
class TemperatureLogitsWarper:
def __init__(self, temperature: float):
if not (temperature > 0):
raise ValueError(f"`temperature` (={temperature}) must be a positive number > 0")
self.temperature = temperature
def __call__(self, scores: torch.FloatTensor) -> torch.FloatTensor:
scores_processed = scores / self.temperature
return scores_processed
class TopPLogitsWarper:
def __init__(self, top_p: float, filter_value: float = -float("Inf"), min_tokens_to_keep: int = 1):
top_p = float(top_p)
if top_p < 0 or top_p > 1.0:
raise ValueError(f"`top_p` has to be a float > 0 and < 1, but is {top_p}")
self.top_p = top_p
self.filter_value = filter_value
self.min_tokens_to_keep = min_tokens_to_keep
def __call__(self, scores: torch.FloatTensor) -> torch.FloatTensor:
sorted_logits, sorted_indices = torch.sort(scores, descending=False)
cumulative_probs = sorted_logits.softmax(dim=-1).cumsum(dim=-1)
sorted_indices_to_remove = cumulative_probs <= (1 - self.top_p)
sorted_indices_to_remove[..., -self.min_tokens_to_keep :] = 0
indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
scores_processed = scores.masked_fill(indices_to_remove, self.filter_value)
return scores_processed
class MinLengthLogitsProcessor:
def __init__(self, min_length: int, eos_token_id: torch.Tensor):
self.min_length = min_length
self.eos_token_id = eos_token_id
def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:
if input_ids is None:
return scores
vocab_tensor = torch.arange(scores.shape[-1], device=scores.device)
eos_token_mask = torch.isin(vocab_tensor, self.eos_token_id)
scores_processed = scores.clone()
if input_ids.shape[-1] < self.min_length:
scores_processed = torch.where(eos_token_mask, -math.inf, scores)
return scores_processed
def get_logits_processing(config: GenerationConfig):
# TODO: add support for beam search with diversity penalty
logits_processors = []
if config._eos_token_tensor is not None and config.min_length > 1:
logits_processors.append(MinLengthLogitsProcessor(config.min_length, config._eos_token_tensor))
if config.top_k is not None and config.top_k != 0:
logits_processors.append(TopKLogits(config.top_k))
if config.temperature is not None and config.temperature != 1:
logits_processors.append(TemperatureLogitsWarper(config.temperature))
if config.top_p is not None and config.top_p != 1:
logits_processors.append(TopPLogitsWarper(config.top_p))
return logits_processors
def apply_logits_processing(input_ids, logits, logits_processing_list, **kwargs):
for process in logits_processing_list:
func_args = inspect.signature(process.__call__).parameters
if not all(arg in kwargs for arg in list(func_args.keys())[3:]):
raise ValueError(
f"Make sure that all the required parameters: {list(func_args.keys())} for "
f"{process.__class__} are passed to the logits processor."
)
if "input_ids" in func_args:
logits = process(input_ids, logits)
else:
logits = process(logits, **kwargs)
return logits
def check_stopping_strings(input_ids: torch.Tensor, stop_strings: list) -> torch.BoolTensor:
# stop_strings must be a list of lists: List[List[], List[]]
device = input_ids.device
batch_size, seq_len = input_ids.shape
finished = torch.zeros(batch_size, dtype = torch.bool, device = device)
for b in range(batch_size):
row = input_ids[b]
# check each stop token sequence
for stop_ids in stop_strings:
n = len(stop_ids)
if n == 0 or n > seq_len:
continue
# compare tail of the generated ids to the stop sequence
if torch.all(row[-n:] == torch.tensor(stop_ids, device = device, dtype = row.dtype)):
finished[b] = True
break
return finished
def check_stopping_criteria(input_ids: torch.Tensor, max_length: int, eos_token, stop_strings: tuple = None):
device = input_ids.device
if not isinstance(eos_token, torch.Tensor):
eos_token = torch.tensor(eos_token, device = device)
max_len_done = input_ids.shape[1] >= max_length
eos_done = torch.isin(input_ids[:, -1], eos_token)
if stop_strings is not None:
stop_done = check_stopping_strings(input_ids, stop_strings)
else:
stop_done = torch.zeros(input_ids.size(0), dtype=torch.bool, device=device)
# finished either by lenght or eos or stop strings
finished_mask = max_len_done | eos_done | stop_done
unfinished_mask = ~finished_mask
return finished_mask, unfinished_mask
class GenerationSampling(Enum):
GREEDY_SEARCH = "greedy_search"
BEAM_SAMPLING = "beam_sample"
@dataclass
class GenerationConfig:
pad_token_id: int = None
bos_token_id: int = None
eos_token_id: int = None
top_k: int = None
top_p: int = None
do_sample: bool = None # TODO: add beam sampling logic
use_cache: bool = True
num_beams: int = 1
temperature: float = 1.0
max_new_length: int = 1024
min_new_length: int = 0
num_return_sequences: int = 1
generation_kwargs = {}
cache_implementation: str = "static"
is_using_cuda_graphs: bool = True
# special values, should be touched only in generation (no config)
max_length = None
min_length = None
_bos_token_tensor = None
_eos_token_tensor = None
_pad_token_tensor = None
def update(self, **kwargs):
to_remove = []
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
to_remove.append(key)
# TODO in case of scaling for beam sampling: add a validation for the user's config
unused_kwargs = {key: value for key, value in kwargs.items() if key not in to_remove}
return unused_kwargs
@classmethod
def from_model_config(cls, config_dict: dict, **kwargs) -> GenerationConfig:
config_dict = {key: value for key, value in config_dict.items() if value is not None}
valid_fields = {f.name for f in fields(cls)}
filtered_args = {k: v for k, v in {**config_dict, **kwargs}.items() if k in valid_fields}
generation_config = cls(**filtered_args)
return generation_config
@dataclass
class CacheConfig:
hidden_size: int
num_attention_heads: int
num_key_value_heads: int
head_dim: int
intermediate_size: int
num_hidden_layers: int
max_batch: int = 1
def get_text_config(self):
return self
class AutoRegressiveGeneration:
def __init__(self, model, device, kv_cache_lengths: list = [1024, 4096, 8192]):
self.device = device
self.dtype = model.dtype
self.model = model
self.model.generation_config = GenerationConfig.from_model_config(self.model.config)
self.model.generation_config.cache_implementation = self.model.cache_implementation
text_config = self.model.cache_config
self.cache_config = CacheConfig(
hidden_size = text_config["hidden_size"],
num_attention_heads = text_config["num_attention_heads"],
num_key_value_heads = text_config["num_key_value_heads"],
head_dim = text_config["head_dim"],
intermediate_size = text_config["intermediate_size"],
num_hidden_layers = self.model.num_hidden_layers or text_config["num_hidden_layers"],
)
self.model.cache_config = self.cache_config
self.kv_caches = {
length: StaticCache(
config=self.cache_config,
max_cache_len=length,
)
for length in sorted(kv_cache_lengths)
} if self.model.cache_implementation == "static" and self.model.use_kv_buckets else None
# for now
self.model.generation_config.is_using_cuda_graphs = False
@torch.inference_mode()
def generate(self, input_ids: Optional[torch.LongTensor] = None, max_new_length: int = 1024, min_new_length = 0,
top_k: int = 50, top_p: float = 1.0, temperature: float = 1.0, do_sample: bool = False, seed = None, **kwargs):
if seed is not None:
torch_generator = torch.Generator(device = input_ids.device).manual_seed(seed)
else:
torch_generator = None
kwargs["torch_generator"] = torch_generator
kwargs["pbar"] = comfy.utils.ProgressBar(max_new_length)
gen_kwargs = dict(
max_new_length = max_new_length,
min_new_length = min_new_length,
top_k = top_k,
top_p = top_p,
temperature = temperature,
do_sample = do_sample
)
# in case the specific model requires special handling
generate_fn = getattr(self.model, "generate", None)
if generate_fn is not None:
generation_config = generate_fn(input_ids, generation_functions = self, **kwargs)
generation_config.update(**gen_kwargs)
if generation_config is None:
generation_config = GenerationConfig(max_new_length = max_new_length,
min_new_length = min_new_length,
top_k = top_k,
top_p = top_p,
do_sample = do_sample,
temperature = temperature)
generation_config, model_kwargs = self._prepare_generation_config(
generation_config, **kwargs
)
accepts_attention_mask = "attention_mask" in set(inspect.signature(self.model.forward).parameters.keys())
requires_attention_mask = "encoder_outputs" not in model_kwargs
kwargs_has_attention_mask = model_kwargs.get("attention_mask", None) is not None
inputs_tensor = input_ids
model_input_name = "input_ids"
batch_size = inputs_tensor.shape[0]
device = inputs_tensor.device
self._prepare_special_tokens(generation_config, device = device)
if not kwargs_has_attention_mask and requires_attention_mask and accepts_attention_mask:
model_kwargs["attention_mask"] = self._prepare_attention_mask_for_generation(
inputs_tensor, generation_config, model_kwargs
)
elif kwargs_has_attention_mask:
if model_input_name == "input_ids" and len(model_kwargs["attention_mask"].shape) > 2:
raise ValueError("`attention_mask` passed to `generate` must be 2D.")
input_ids_length = input_ids.shape[1]
generation_config = self._prepare_generated_length(
generation_config=generation_config,
input_ids_length=input_ids_length,
)
self._validate_generated_length(generation_config, input_ids_length)
max_cache_length = generation_config.max_length - 1
self._prepare_cache_for_generation(
generation_config, model_kwargs, batch_size, max_cache_length, device
)
generation_mode = self.get_generation_mode(generation_config)
prepared_logits_processor = get_logits_processing(generation_config)
model_kwargs["use_cache"] = generation_config.use_cache
if generation_mode == GenerationSampling.GREEDY_SEARCH:
input_ids, model_kwargs = self._expand_inputs_for_generation(
input_ids = input_ids,
expand_size = generation_config.num_return_sequences,
**model_kwargs,
)
# TODO: have a default self._sample fn and a default check if the model supports autoregGen or not
if not hasattr(self.model, "_sample"):
raise ValueError("Model doesn't support AutoRegressive Generation!")
self._prepare_kv_caches()
result = self.model._sample(
input_ids,
logits_processing_list = prepared_logits_processor,
generation_config = generation_config,
past_key_values_buckets = self.kv_caches,
**model_kwargs,
)
return result
def _prepare_kv_caches(self):
for kv_cache in self.kv_caches.values():
kv_cache.reset()
def get_generation_mode(self, config: GenerationConfig):
if config.num_beams > 1:
return GenerationSampling.BEAM_SAMPLING
else:
return GenerationSampling.GREEDY_SEARCH
def _prepare_generated_length(
self,
generation_config: GenerationConfig,
input_ids_length,
):
""" max_length = user_input_id_tokens + generation_max_length """
if generation_config.max_new_length is not None:
generation_config.max_length = generation_config.max_new_length + input_ids_length
# same for max length
if generation_config.min_new_length is not None:
generation_config.min_length = generation_config.min_new_length + input_ids_length
return generation_config
def _get_cache(
self, cache_implementation: str, batch_size: int, max_cache_len: int, device: torch.device, model_kwargs
):
assert cache_implementation == "static", f"Only 'static' cache is supported, got {cache_implementation}"
cache_cls = NEED_SETUP_CACHE_CLASSES_MAPPING[cache_implementation]
if hasattr(self.model, "_cache"):
cache_to_check = self.model._cache
else:
cache_to_check = None
need_new_cache = (
not hasattr(self.model, "_cache")
or (not isinstance(cache_to_check, cache_cls))
or (self.cache_config.max_batch != batch_size)
or (cache_to_check.max_cache_len < max_cache_len)
)
if need_new_cache:
cache_kwargs = {
"config": self.cache_config,
"max_cache_len": max_cache_len,
}
self.model._cache = cache_cls(**cache_kwargs)
else:
self.model._cache.reset()
return self.model._cache
def _prepare_cache_for_generation(
self,
generation_config: GenerationConfig,
model_kwargs: dict,
batch_size: int,
max_cache_length: int,
device: torch.device,
) -> bool:
cache_name = "past_key_values"
if generation_config.use_cache is False:
return
if generation_config.cache_implementation is not None:
if generation_config.cache_implementation in NEED_SETUP_CACHE_CLASSES_MAPPING:
model_kwargs[cache_name] = self._get_cache(
cache_implementation=generation_config.cache_implementation,
batch_size=max(generation_config.num_beams, generation_config.num_return_sequences) * batch_size,
max_cache_len=max_cache_length,
device=device,
model_kwargs=model_kwargs,
)
elif generation_config.cache_implementation == "dynamic":
model_kwargs[cache_name] = DynamicCache()
def _prepare_generation_config(self, generation_config: GenerationConfig, **kwargs: dict) -> tuple[GenerationConfig, dict]:
generation_config = copy.deepcopy(generation_config)
modified_values = {}
global_default_generation_config = GenerationConfig()
model_generation_config = self.model.generation_config
for key, model_gen_config_value in model_generation_config.__dict__.items():
if key.startswith("_"):
continue
global_default_value = getattr(global_default_generation_config, key, None)
custom_gen_config_value = getattr(generation_config, key, None)
if (
custom_gen_config_value == global_default_value
and model_gen_config_value != global_default_value
):
modified_values[key] = model_gen_config_value
setattr(generation_config, key, model_gen_config_value)
if generation_config.temperature == 0.0:
generation_config.do_sample = False
model_kwargs = generation_config.update(**kwargs)
return generation_config, model_kwargs
def _validate_generated_length(self, generation_config: GenerationConfig, input_ids_length):
"""Performs validation related to the resulting generated length"""
if input_ids_length >= generation_config.max_length:
input_ids_string = "input_ids"
raise ValueError(
f"Input length of {input_ids_string} is {input_ids_length}, but `max_length` is set to"
f" {generation_config.max_length}. This can lead to unexpected behavior. You should consider"
" increasing `max_length` or, better yet, setting `max_new_tokens`."
)
min_length_error_suffix = (
" Generation will stop at the defined maximum length. You should decrease the minimum length and/or "
"increase the maximum length."
)
if generation_config.min_length is not None and generation_config.min_length > generation_config.max_length:
warnings.warn(
f"Unfeasible length constraints: `min_length` ({generation_config.min_length}) is larger than"
f" the maximum possible length ({generation_config.max_length})." + min_length_error_suffix,
UserWarning,
)
if generation_config.min_new_length is not None:
min_length = generation_config.min_new_length + input_ids_length
if min_length > generation_config.max_length:
warnings.warn(
f"Unfeasible length constraints: `min_new_length` ({generation_config.min_new_length}), when "
f"added to the prompt length ({input_ids_length}), is larger than"
f" the maximum possible length ({generation_config.max_length})." + min_length_error_suffix,
UserWarning,
)
def _expand_inputs_for_generation(
self,
expand_size: int = 1,
input_ids: Optional[torch.LongTensor] = None,
**model_kwargs,
) -> tuple[torch.LongTensor, dict[str, Any]]:
""" Expands tensors from [batch_size, ...] to [batch_size * expand_size, ...] """
if expand_size == 1:
return input_ids, model_kwargs
def _expand_dict_for_generation(dict_to_expand):
for key in dict_to_expand:
if (
key != "cache_position"
and dict_to_expand[key] is not None
and isinstance(dict_to_expand[key], torch.Tensor)
):
dict_to_expand[key] = dict_to_expand[key].repeat_interleave(expand_size, dim=0)
return dict_to_expand
if input_ids is not None:
input_ids = input_ids.repeat_interleave(expand_size, dim=0)
model_kwargs = _expand_dict_for_generation(model_kwargs)
return input_ids, model_kwargs
def _prepare_special_tokens(
self,
generation_config: GenerationConfig,
device: Optional[Union[torch.device, str]] = None,
):
def _tensor_or_none(token, device=None):
if token is None:
return token
device = device if device is not None else self.device
if isinstance(token, torch.Tensor):
return token.to(device)
return torch.tensor(token, device=device, dtype=torch.long)
bos_token_tensor = _tensor_or_none(generation_config.bos_token_id, device=device)
eos_token_tensor = _tensor_or_none(generation_config.eos_token_id, device=device)
pad_token_tensor = _tensor_or_none(generation_config.pad_token_id, device=device)
# We can have more than one eos token. Always treat it as a 1D tensor (when it exists).
if eos_token_tensor is not None and eos_token_tensor.ndim == 0:
eos_token_tensor = eos_token_tensor.unsqueeze(0)
if pad_token_tensor is None and eos_token_tensor is not None:
pad_token_tensor = eos_token_tensor[0]
# to avoid problems with cuda graphs
generation_config._bos_token_tensor = bos_token_tensor
generation_config._eos_token_tensor = eos_token_tensor
generation_config._pad_token_tensor = pad_token_tensor
def _prepare_attention_mask_for_generation(
self,
inputs_tensor: torch.Tensor,
generation_config: GenerationConfig,
model_kwargs: dict[str, Any],
) -> torch.LongTensor:
pad_token_id = generation_config._pad_token_tensor
eos_token_id = generation_config._eos_token_tensor
if "input_ids" in model_kwargs and model_kwargs["input_ids"].shape[1] > 0:
inputs_tensor = model_kwargs["input_ids"]
# No information for attention mask inference -> return default attention mask
default_attention_mask = torch.ones(inputs_tensor.shape[:2], dtype=torch.long, device=inputs_tensor.device)
if pad_token_id is None:
return default_attention_mask
is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long]
if not is_input_ids:
return default_attention_mask
is_pad_token_in_inputs = (pad_token_id is not None) and (
torch.isin(elements=inputs_tensor, test_elements=pad_token_id).any()
)
is_pad_token_not_equal_to_eos_token_id = (eos_token_id is None) or ~(
torch.isin(elements=eos_token_id, test_elements=pad_token_id).any()
)
can_infer_attention_mask = is_pad_token_in_inputs * is_pad_token_not_equal_to_eos_token_id
attention_mask_from_padding = inputs_tensor.ne(pad_token_id).long()
attention_mask = (
attention_mask_from_padding * can_infer_attention_mask + default_attention_mask * ~can_infer_attention_mask
)
return attention_mask
def auto_sample(node, patcher, input_ids, max_new_length=1024, min_new_length=0, top_k=50, top_p=1.0, temperature=1.0, do_sample = False, seed=None, **kwargs):
# to work with BaseModel
if hasattr(patcher, "model") and hasattr(patcher.model, "diffusion_model"):
model = patcher.model.diffusion_model
if node._cached_autoregressive_sampler is None or node._cached_autoregressive_sampler.model is not model:
if model.device != patcher.load_device:
model = model.to(patcher.load_device, dtype=model.dtype)
model.device = patcher.load_device
node._cached_autoregressive_sampler = AutoRegressiveGeneration(model, device = patcher.load_device)
if isinstance(input_ids, dict):
main_input_ids = input_ids.get("input_ids")
kwargs.update({k: v for k, v in input_ids.items() if k != "input_ids"})
else:
main_input_ids = input_ids
device = node._cached_autoregressive_sampler.device
main_input_ids = main_input_ids.to(device)
for k, v in kwargs.items():
if isinstance(v, torch.Tensor):
kwargs[k] = v.to(device)
# for tokenizers.Encoding output
if not isinstance(main_input_ids, torch.Tensor):
kwargs["attention_mask"] = torch.tensor(main_input_ids["attention_mask"])
main_input_ids = torch.tensor(main_input_ids["input_ids"])
samples = node._cached_autoregressive_sampler.generate(main_input_ids, max_new_length, min_new_length, top_k, top_p, temperature, do_sample, seed=seed, **kwargs)
if isinstance(samples, list):
samples = [sample.to(comfy.model_management.intermediate_device()) for sample in samples]
else:
samples = samples.to(comfy.model_management.intermediate_device())
return samples

View File

@ -0,0 +1,311 @@
import copy
import math
import torch
import scipy
import numpy as np
import torch.nn.functional as F
from typing import Optional, List
# defaulted to the new pytorch api
def _new_rfft(x: torch.Tensor):
z = torch.fft.rfft(x, dim=-1)
return torch.view_as_real(z)
def _new_irfft(x: torch.Tensor, length: int):
x = torch.view_as_complex(x)
return torch.fft.irfft(x, length, dim=-1)
def _compl_mul_conjugate(a: torch.Tensor, b: torch.Tensor):
# changed this function to use the pytorch api
return torch.view_as_real(torch.view_as_complex(a) * torch.view_as_complex(b).conj())
def unfold(input, kernel_size: int, stride: int):
shape = list(input.shape)
length = shape.pop(-1)
n_frames = math.ceil((max(length, kernel_size) - kernel_size) / stride) + 1
tgt_length = (n_frames - 1) * stride + kernel_size
padded = F.pad(input, (0, tgt_length - length)).contiguous()
strides: List[int] = []
for dim in range(padded.dim()):
strides.append(padded.stride(dim))
last_stride = strides.pop(-1)
assert last_stride == 1, 'data should be contiguous'
strides = strides + [stride, 1]
return padded.as_strided(shape + [n_frames, kernel_size], strides)
# convert the signal and filter to frequency domain, multiply them, then inverse FFT to get back to time-domain
# faster than a sliding window over time-domain.
def fft_conv1d(
input: torch.Tensor, weight: torch.Tensor,
bias: Optional[torch.Tensor] = None, stride: int = 1, padding: int = 0,
block_ratio: float = 5):
input = F.pad(input, (padding, padding))
batch, _, length = input.shape
out_channels, _, kernel_size = weight.shape
_rfft = _new_rfft
_irfft = _new_irfft
if length < kernel_size:
raise RuntimeError(f"Input should be at least as large as the kernel size {kernel_size}, "
f"but it is only {length} samples long.")
if block_ratio < 1:
raise RuntimeError("Block ratio must be greater than 1.")
# We are going to process the input blocks by blocks, as for some reason it is faster
# and less memory intensive (I think the culprit is `torch.einsum`.
block_size: int = min(int(kernel_size * block_ratio), length)
fold_stride = block_size - kernel_size + 1
# replaces to_pad
weight = F.pad(weight, (0, block_size - weight.shape[-1]), mode = "constant", value = 0)
weight_z = _rfft(weight)
# We pad the input and get the different frames, on which
frames = unfold(input, block_size, fold_stride)
frames_z = _rfft(frames)
out_z = _compl_mul_conjugate(frames_z, weight_z)
out = _irfft(out_z, block_size)
# The last bit is invalid, because FFT will do a circular convolution.
out = out[..., :-kernel_size + 1]
out = out.reshape(batch, out_channels, -1)
out = out[..., ::stride]
target_length = (length - kernel_size) // stride + 1
out = out[..., :target_length]
if bias is not None:
out += bias[:, None]
return out
class IIRfilter(object):
def __init__(self, G, Q, fc, rate, filter_type, passband_gain=1.0):
G, Q, fc, rate = [t if isinstance(t, torch.Tensor) else torch.as_tensor(t) for t in (G, Q, fc, rate)]
self.G = G
self.Q = Q
self.fc = fc
self.rate = rate
self.filter_type = filter_type
self.passband_gain = passband_gain
def generate_coefficients(self):
A = 10**(self.G/40.0)
w0 = 2.0 * torch.pi * (self.fc / self.rate)
alpha = torch.sin(w0) / (2.0 * self.Q)
if self.filter_type == 'high_shelf':
b0 = A * ( (A+1) + (A-1) * torch.cos(w0) + 2 * torch.sqrt(A) * alpha )
b1 = -2 * A * ( (A-1) + (A+1) * torch.cos(w0) )
b2 = A * ( (A+1) + (A-1) * torch.cos(w0) - 2 * torch.sqrt(A) * alpha )
a0 = (A+1) - (A-1) * torch.cos(w0) + 2 * torch.sqrt(A) * alpha
a1 = 2 * ( (A-1) - (A+1) * torch.cos(w0) )
a2 = (A+1) - (A-1) * torch.cos(w0) - 2 * torch.sqrt(A) * alpha
elif self.filter_type == 'high_pass':
b0 = (1 + torch.cos(w0))/2
b1 = -(1 + torch.cos(w0))
b2 = (1 + torch.cos(w0))/2
a0 = 1 + alpha
a1 = -2 * torch.cos(w0)
a2 = 1 - alpha
return torch.tensor([b0, b1, b2])/a0, torch.tensor([a0, a1, a2])/a0
def apply_filter(self, data):
return self.passband_gain * scipy.signal.lfilter(self.b, self.a, data)
@property
def b_and_a(self):
return self.generate_coefficients()
class Meter(torch.nn.Module):
def __init__(
self,
rate: int,
filter_class: str = "K-weighting",
block_size: float = 0.400,
zeros: int = 512,
use_fir: bool = False,
):
super().__init__()
self.rate = rate
self.filter_class = filter_class
self.block_size = block_size
self.use_fir = use_fir
G = torch.from_numpy(np.array([1.0, 1.0, 1.0, 1.41, 1.41]))
self.register_buffer("G", G)
self._filters = {}
self._filters['high_shelf'] = IIRfilter(4.0, 1/np.sqrt(2), 1500.0, self.rate, 'high_shelf')
self._filters['high_pass'] = IIRfilter(0.0, 0.5, 38.0, self.rate, 'high_pass')
# Compute impulse responses so that filtering is fast via
# a convolution at runtime, on GPU, unlike lfilter.
impulse = np.zeros((zeros,))
impulse[..., 0] = 1.0
firs = np.zeros((len(self._filters), 1, zeros))
passband_gain = torch.tensor([filter.passband_gain for filter in self._filters.values()])
for i, (_, filter_stage) in enumerate(self._filters.items()):
b, a = filter_stage.b_and_a
firs[i] = scipy.signal.lfilter(b.numpy(), a.numpy(), impulse)
firs = torch.from_numpy(firs[..., ::-1].copy()).float()
self.register_buffer("firs", firs)
self.register_buffer("passband_gain", passband_gain)
def apply_filter_fir(self, data: torch.Tensor):
# Data is of shape (nb, nch, nt)
# Reshape to (nb*nch, 1, nt)
nb, nt, nch = data.shape
data = data.permute(0, 2, 1)
data = data.reshape(nb * nch, 1, nt)
# Apply padding
pad_length = self.firs.shape[-1]
# Apply filtering in sequence
for i in range(self.firs.shape[0]):
data = F.pad(data, (pad_length, pad_length))
data = fft_conv1d(data, self.firs[i, None, ...])
data = self.passband_gain[i] * data
data = data[..., 1 : nt + 1]
data = data.permute(0, 2, 1)
data = data[:, :nt, :]
return data
def apply_filter(self, data: torch.Tensor):
return self.apply_filter_fir(data)
def forward(self, data: torch.Tensor):
return self.integrated_loudness(data)
def _unfold(self, input_data):
T_g = self.block_size
overlap = 0.75 # overlap of 75% of the block duration
step = 1.0 - overlap # step size by percentage
kernel_size = int(T_g * self.rate)
stride = int(T_g * self.rate * step)
unfolded = unfold(input_data.permute(0, 2, 1), kernel_size, stride)
unfolded = unfolded.transpose(-1, -2)
return unfolded
def integrated_loudness(self, data: torch.Tensor):
if not torch.is_tensor(data):
data = torch.from_numpy(data).float()
else:
data = data.float()
input_data = copy.copy(data)
# Data always has a batch and channel dimension.
# Is of shape (nb, nt, nch)
if input_data.ndim < 2:
input_data = input_data.unsqueeze(-1)
if input_data.ndim < 3:
input_data = input_data.unsqueeze(0)
nb, _, nch = input_data.shape
# Apply frequency weighting filters - account
# for the acoustic respose of the head and auditory system
input_data = self.apply_filter(input_data)
G = self.G # channel gains
T_g = self.block_size # 400 ms gating block standard
Gamma_a = -70.0 # -70 LKFS = absolute loudness threshold
unfolded = self._unfold(input_data)
z = (1.0 / (T_g * self.rate)) * unfolded.square().sum(2)
l = -0.691 + 10.0 * torch.log10((G[None, :nch, None] * z).sum(1, keepdim=True))
l = l.expand_as(z)
# find gating block indices above absolute threshold
z_avg_gated = z
z_avg_gated[l <= Gamma_a] = 0
masked = l > Gamma_a
z_avg_gated = z_avg_gated.sum(2) / masked.sum(2)
# calculate the relative threshold value (see eq. 6)
Gamma_r = (
-0.691 + 10.0 * torch.log10((z_avg_gated * G[None, :nch]).sum(-1)) - 10.0
)
Gamma_r = Gamma_r[:, None, None]
Gamma_r = Gamma_r.expand(nb, nch, l.shape[-1])
# find gating block indices above relative and absolute thresholds (end of eq. 7)
z_avg_gated = z
z_avg_gated[l <= Gamma_a] = 0
z_avg_gated[l <= Gamma_r] = 0
masked = (l > Gamma_a) * (l > Gamma_r)
z_avg_gated = z_avg_gated.sum(2) / masked.sum(2)
# # Cannot use nan_to_num (pytorch 1.8 does not come with GCP-supported cuda version)
# z_avg_gated = torch.nan_to_num(z_avg_gated)
z_avg_gated = torch.where(
z_avg_gated.isnan(), torch.zeros_like(z_avg_gated), z_avg_gated
)
z_avg_gated[z_avg_gated == float("inf")] = float(np.finfo(np.float32).max)
z_avg_gated[z_avg_gated == -float("inf")] = float(np.finfo(np.float32).min)
LUFS = -0.691 + 10.0 * torch.log10((G[None, :nch] * z_avg_gated).sum(1))
return LUFS.float()
def loudness(
audio_data, sample_rate: int, target_loudness: int, filter_class: str = "K-weighting", block_size: float = 0.400, **kwargs
):
MIN_LOUDNESS = -70
device = audio_data.device
original_length = audio_data.shape[-1]
signal_duration = original_length / sample_rate
# Pad if too short
if signal_duration < 0.5:
pad_len = int((0.5 - signal_duration) * sample_rate)
audio_data = torch.nn.functional.pad(audio_data, (0, pad_len), mode="constant", value=0)
# create BS.1770 meter
meter = Meter(
sample_rate, filter_class=filter_class, block_size=block_size, **kwargs
)
meter = meter.to(audio_data.device)
# measure loudness
loudness = meter.integrated_loudness(audio_data.permute(0, 2, 1))
audio_data = audio_data[..., :original_length]
min_loudness = (
torch.ones_like(loudness, device=loudness.device) * MIN_LOUDNESS
)
_loudness = torch.maximum(loudness, min_loudness)
_loudness = _loudness.to(device)
delta_loudness = target_loudness - _loudness
gain = torch.pow(torch.tensor(10.0, device=device, dtype=audio_data.dtype), delta_loudness / 20.0)
output = gain * audio_data
if torch.max(torch.abs(output)) >= 1.0:
import warnings
warnings.warn("Possible clipped samples in output.")
return output

1252
comfy/ldm/higgsv2/model.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,727 @@
from typing import (
Dict, List, Optional, Union, Tuple, MutableMapping, Any, Mapping, Collection, get_type_hints, get_args, get_origin
)
from dataclasses import dataclass, fields, _FIELDS, _FIELD, _FIELD_INITVAR, is_dataclass
import torch.nn.functional as F
import numpy as np
import torch
import math
from functools import lru_cache
__MAX_SIZE = 2048
def _ceil_to_nearest(n, round_to):
return (n + round_to - 1) // round_to * round_to
@torch.jit.script
def build_delay_pattern_mask(
input_ids: torch.Tensor,
bos_token_id: int,
pad_token_id: int,
):
bsz, num_codebooks, seq_len = input_ids.shape
new_seq_len = seq_len + num_codebooks - 1
input_ids_with_gen_mask = torch.ones((bsz, num_codebooks, new_seq_len), dtype=torch.long, device=input_ids.device)
bos_mask = torch.tril(input_ids_with_gen_mask, -1) > 0
eos_mask = torch.triu(input_ids_with_gen_mask, seq_len) > 0
input_ids_with_gen_mask[bos_mask] = bos_token_id
input_ids_with_gen_mask[(~bos_mask) & (~eos_mask)] = input_ids.reshape(-1)
input_ids = input_ids_with_gen_mask.clone()
input_ids[eos_mask] = pad_token_id
input_ids_with_gen_mask[eos_mask] = -1
return input_ids, input_ids_with_gen_mask
# implementation of dacite's from_dict with only necessary parts for ChatML
@lru_cache(maxsize=None)
def cache(function):
return lru_cache(maxsize=__MAX_SIZE, typed=True)(function)
@cache
def get_fields(data_class):
fields = getattr(data_class, _FIELDS)
return [f for f in fields.values() if f._field_type is _FIELD or f._field_type is _FIELD_INITVAR]
def is_optional(type_) -> bool:
return get_origin(type_) is Union and type(None) in get_args(type_)
def orig(data_class) -> Any:
if is_dataclass(data_class):
return data_class
return get_origin(data_class)
@cache
def extract_generic(type_, defaults: Tuple = ()) -> tuple:
try:
if getattr(type_, "_special", False):
return defaults
if type_.__args__ == ():
return (type_.__args__,)
return type_.__args__ or defaults # type: ignore
except AttributeError:
return defaults
def _build_value_for_collection(collection, data: Any) -> Any:
if isinstance(data, Mapping):
value_type = extract_generic(collection, defaults=(Any, Any))[1]
return {
key: _build_value(type_=value_type, data=value)
for key, value in data.items()
}
elif isinstance(data, Collection) and not isinstance(data, (str, bytes, Mapping)):
item_type = extract_generic(collection, defaults=(Any,))[0]
return [
_build_value(type_=item_type, data=item)
for item in data
]
return data
def _build_value(type_, data) -> Any:
if is_optional(type_) and data is None:
return data
if get_origin(type_) is Union:
data = _build_value_for_union(union=type_, data=data)
elif hasattr(type_, "__origin__"):
data = _build_value_for_collection(collection=type_, data=data)
elif cache(is_dataclass)(orig(type_)) and isinstance(data, Mapping):
data = from_dict(data_class=type_, data=data)
return data
def _build_value_for_union(union: type, data: Any) -> Any:
for inner_type in get_args(union):
if data is None and inner_type is type(None):
return None
try:
return _build_value(inner_type, data)
except Exception:
continue
raise ValueError(f"Cannot match {data!r} to any type in {union}")
def is_instance(value: Any, type_) -> bool:
if type_ is Any:
return True
origin = get_origin(type_)
args = get_args(type_)
if origin is Union:
return any(is_instance(value, arg) for arg in args)
if origin in (list, List):
if not isinstance(value, list):
return False
(elem_type,) = args or (Any,)
return all(is_instance(item, elem_type) for item in value)
if origin in (dict, Dict, Mapping):
if not isinstance(value, dict):
return False
key_type, val_type = args or (Any, Any)
return all(
is_instance(k, key_type) and is_instance(v, val_type)
for k, v in value.items()
)
try:
return isinstance(value, type_)
except TypeError:
return False
def from_dict(data_class, data):
init_values: MutableMapping[str, Any] = {}
post_init_values: MutableMapping[str, Any] = {}
data_class_hints = get_type_hints(data_class)
data_class_fields = cache(get_fields)(data_class)
extra_fields = set(data.keys()) - {f.name for f in data_class_fields}
if extra_fields:
formatted_keys = ", ".join(f'"{key}"' for key in extra_fields)
raise ValueError(f"cannot match {formatted_keys} to any data class field")
for field in data_class_fields:
field_type = data_class_hints[field.name]
key = field.name
if key in data:
try:
value = _build_value(type_=field_type, data=data[key])
except Exception as error:
raise ValueError(error)
if not is_instance(value, field_type):
raise ValueError((
f'wrong value type for field "{field.name}" - should be "{field_type}" '
f'instead of value "{value}" of type "{type(value)}"'
))
init_values[field.name] = value
instance = data_class(**init_values)
for key, value in post_init_values.items():
setattr(instance, key, value)
return instance
def normalize_chinese_punctuation(text):
"""
Convert Chinese (full-width) punctuation marks to English (half-width) equivalents.
"""
# Mapping of Chinese punctuation to English punctuation
chinese_to_english_punct = {
"": ", ", # comma
"": ".", # period
"": ":", # colon
"": ";", # semicolon
"": "?", # question mark
"": "!", # exclamation mark
"": "(", # left parenthesis
"": ")", # right parenthesis
"": "[", # left square bracket
"": "]", # right square bracket
"": "<", # left angle quote
"": ">", # right angle quote
"": '"', # left double quotation
"": '"', # right double quotation
"": "'", # left single quotation
"": "'", # right single quotation
"": ",", # enumeration comma
"": "-", # em dash
"": "...", # ellipsis
"·": ".", # middle dot
"": '"', # left corner bracket
"": '"', # right corner bracket
"": '"', # left double corner bracket
"": '"', # right double corner bracket
}
# Replace each Chinese punctuation with its English counterpart
for zh_punct, en_punct in chinese_to_english_punct.items():
text = text.replace(zh_punct, en_punct)
return text
def transcript_normalize(text: str):
transcript = normalize_chinese_punctuation(text)
transcript = transcript.replace("(", " ")
transcript = transcript.replace(")", " ")
transcript = transcript.replace("°F", " degrees Fahrenheit")
transcript = transcript.replace("°C", " degrees Celsius")
for tag, replacement in [
("[laugh]", "<SE>[Laughter]</SE>"),
("[humming start]", "<SE_s>[Humming]</SE_s>"),
("[humming end]", "<SE_e>[Humming]</SE_e>"),
("[music start]", "<SE_s>[Music]</SE_s>"),
("[music end]", "<SE_e>[Music]</SE_e>"),
("[music]", "<SE>[Music]</SE>"),
("[sing start]", "<SE_s>[Singing]</SE_s>"),
("[sing end]", "<SE_e>[Singing]</SE_e>"),
("[applause]", "<SE>[Applause]</SE>"),
("[cheering]", "<SE>[Cheering]</SE>"),
("[cough]", "<SE>[Cough]</SE>"),
]:
transcript = transcript.replace(tag, replacement)
lines = transcript.split("\n")
transcript = "\n".join([" ".join(line.split()) for line in lines if line.strip()])
transcript = transcript.strip()
if not any([transcript.endswith(c) for c in [".", "!", "?", ",", ";", '"', "'", "</SE_e>", "</SE>"]]):
transcript += "."
return transcript
@dataclass
class AudioContent:
audio_url: str
raw_audio: Optional[str] = None
offset: Optional[float] = None
duration: Optional[float] = None
row_id: Optional[int] = None
type: str = "audio"
@dataclass
class TextContent:
text: str
type: str = "text"
@dataclass
class Message:
role: str
content: Union[str, AudioContent, TextContent, List[Union[str, AudioContent, TextContent]]]
recipient: Optional[str] = None
@dataclass
class ChatMLSample:
messages: List[Message]
start_index: Optional[int] = None
misc: Optional[Dict] = None
speaker: Optional[str] = None
def prepare_chatml_sample(sample: Union[ChatMLSample, Dict], tokenizer):
try:
if not isinstance(sample, ChatMLSample):
# replacing pd.isna
def is_nan(x):
if isinstance(x, float):
return math.isnan(x)
if isinstance(x, np.generic):
return np.isnan(x)
if isinstance(x, torch.Tensor) and x.numel() == 1:
return torch.isnan(x).item()
return False
if "speaker" in sample and is_nan(sample["speaker"]):
sample["speaker"] = None
if "start_index" in sample and is_nan(sample["start_index"]):
sample["start_index"] = None
if "content" in sample and is_nan(sample["content"]):
sample["content"] = ""
def convert_nan_to_none(obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, float) and math.isnan(obj):
return None
elif isinstance(obj, dict):
return {k: convert_nan_to_none(v) for k, v in obj.items()}
elif isinstance(obj, (list, tuple)):
return [convert_nan_to_none(item) for item in obj]
return obj
clean_sample = convert_nan_to_none(sample)
val_keys = []
for field in fields(ChatMLSample):
if field.name in clean_sample:
val_keys.append(field.name)
clean_sample = {k: clean_sample[k] for k in val_keys}
sample = from_dict(
data_class=ChatMLSample, data=clean_sample,
)
input_tokens = []
audio_contents = []
speaker_id = None
if sample.speaker is not None:
speaker_id = sample.speaker
elif sample.misc is not None:
if "speaker" in sample.misc:
speaker_id = sample.misc["speaker"]
total_m = len(sample.messages)
for turn_id, message in enumerate(sample.messages):
role = message.role
recipient = message.recipient
content = message.content
content_l = []
if isinstance(content, str):
content_l.append(TextContent(text=content))
elif isinstance(content, TextContent):
content_l.append(content)
elif isinstance(content, AudioContent):
content_l.append(content)
elif isinstance(content, list):
for ele in content:
if isinstance(ele, str):
content_l.append(TextContent(text=ele))
else:
content_l.append(ele)
if turn_id == 0:
prefix = f"<|begin_of_text|><|start_header_id|>{role}<|end_header_id|>\n\n"
else:
prefix = f"<|start_header_id|>{role}<|end_header_id|>\n\n"
eot_postfix = "<|eot_id|>"
eom_postfix = "<|eom_id|>"
prefix_tokens = tokenizer.encode(prefix, add_special_tokens=False)
input_tokens.extend(prefix_tokens)
if recipient:
assert role == "assistant", "Recipient is only available for assistant role."
recipient_tokens = tokenizer.encode(f"{recipient}<|recipient|>", add_special_tokens=False)
input_tokens.extend(recipient_tokens)
for content in content_l:
if content.type == "text":
text_tokens = tokenizer.encode(content.text, add_special_tokens=False)
input_tokens.extend(text_tokens)
elif content.type == "audio":
audio_contents.append(content)
if role == "user" or role == "system":
text_tokens = tokenizer.encode(
"<|audio_bos|><|AUDIO|><|audio_eos|>",
add_special_tokens=False,
)
input_tokens.extend(text_tokens)
elif role == "assistant":
text_tokens = tokenizer.encode(
"<|audio_out_bos|><|AUDIO_OUT|><|audio_eos|>",
add_special_tokens=False,
)
input_tokens.extend(text_tokens)
next_id = turn_id + 1
if role == "assistant" and next_id != total_m and sample.messages[next_id].role == "assistant":
postfix_tokens = tokenizer.encode(eom_postfix, add_special_tokens=False)
input_tokens.extend(postfix_tokens)
else:
postfix_tokens = tokenizer.encode(eot_postfix, add_special_tokens=False)
input_tokens.extend(postfix_tokens)
return input_tokens, audio_contents, speaker_id
except Exception:
return None, None, None
@dataclass
class HiggsAudioBatchInput:
input_ids: torch.LongTensor # shape (bsz, seq_len).
attention_mask: torch.Tensor # shape (bsz, seq_len).
audio_out_ids: Optional[torch.LongTensor] # shape (num_codebooks, audio_out_total_length)
audio_out_ids_start: Optional[torch.LongTensor] # shape (num_audio_out,)
audio_out_ids_start_group_loc: Optional[torch.LongTensor] # shape (num_audio_out,), specify which a sample's group location in the batch
audio_in_ids: Optional[torch.LongTensor] # shape (num_codebooks, audio_in_total_length)
audio_in_ids_start: Optional[torch.LongTensor] # shape (num_audio_in,)
label_ids: Optional[torch.LongTensor] # shape (bsz, seq_len)
label_audio_ids: Optional[torch.LongTensor] # shape (num_codebooks, audio_out_total_length)
reward: Optional[float] = None
@dataclass
class ChatMLDatasetSample:
input_ids: torch.LongTensor # (seq_len,) Input text tokens
label_ids: torch.LongTensor # (seq_len,) Label IDs
audio_ids_concat: torch.LongTensor # (num_codebooks, audio_seq_len) Concatenated audio tokens
audio_ids_start: torch.LongTensor # (num_audios,) Start index of each audio token in `audio_ids_concat`
audio_waveforms_concat: torch.Tensor # (total_wv_length,) Concatenated audio waveforms
audio_waveforms_start: torch.LongTensor # (num_audios,) Start index of each waveform in `audio_waveforms_concat`
audio_sample_rate: torch.Tensor # (num_audios,) Sampling rate per audio waveform
audio_speaker_indices: torch.LongTensor # (num_audios,) Speaker indices per audio; -1 = unknown
audio_label_ids_concat: Optional[torch.LongTensor] = None # (num_codebooks, audio_seq_len) Optional audio token labels
reward: Optional[float] = None # Optional scalar reward
def num_audios(self):
return max(len(self.audio_waveforms_start), len(self.audio_ids_start))
def get_audio_codes(self, idx):
code_start = self.audio_ids_start[idx]
if idx < len(self.audio_ids_start) - 1:
code_end = self.audio_ids_start[idx + 1]
else:
code_end = self.audio_ids_concat.shape[-1]
return self.audio_ids_concat[:, code_start:code_end]
def get_audio_codes_labels(self, idx):
if self.audio_label_ids_concat is None:
return None
code_start = self.audio_ids_start[idx]
if idx < len(self.audio_ids_start) - 1:
code_end = self.audio_ids_start[idx + 1]
else:
code_end = self.audio_ids_concat.shape[-1]
return self.audio_label_ids_concat[:, code_start:code_end]
def get_wv(self, idx):
wv_start = self.audio_waveforms_start[idx]
sr = self.audio_sample_rate[idx]
if idx < len(self.audio_waveforms_start) - 1:
wv_end = self.audio_waveforms_start[idx + 1]
else:
wv_end = self.audio_waveforms_concat.shape[-1]
return self.audio_waveforms_concat[wv_start:wv_end], sr
class HiggsAudioSampleCollator:
def __init__(
self,
audio_in_token_id,
audio_out_token_id,
pad_token_id,
audio_stream_bos_id,
audio_stream_eos_id,
round_to=8,
pad_left=False,
return_audio_in_tokens=True,
audio_num_codebooks=None,
use_delay_pattern=False,
disable_audio_codes_transform=False,
add_new_bos_eos_for_long_chunk=True,
mask_audio_out_token_label=True,
):
self.round_to = round_to
self.pad_left = pad_left
self.audio_in_token_id = audio_in_token_id
self.audio_out_token_id = audio_out_token_id
self.audio_stream_bos_id = audio_stream_bos_id
self.audio_stream_eos_id = audio_stream_eos_id
self.pad_token_id = pad_token_id
self.return_audio_in_tokens = return_audio_in_tokens
self.audio_num_codebooks = audio_num_codebooks
self.use_delay_pattern = use_delay_pattern
self.disable_audio_codes_transform = disable_audio_codes_transform
self.add_new_bos_eos_for_long_chunk = add_new_bos_eos_for_long_chunk
self.mask_audio_out_token_label = mask_audio_out_token_label
def _process_and_duplicate_audio_tokens(
self, input_ids: torch.Tensor, audio_idx: int, wv: torch.Tensor, labels: Optional[torch.Tensor] = None
) -> Tuple[torch.Tensor, torch.Tensor, int]:
total_samples = len(wv)
num_chunks = math.ceil(total_samples / self.chunk_size_samples)
if num_chunks <= 1:
return input_ids, labels, 1
audio_token_seq = input_ids[audio_idx - 1 : audio_idx + 2]
duplicated_sequence = audio_token_seq.repeat(num_chunks)
new_input_ids = torch.cat([input_ids[: audio_idx - 1], duplicated_sequence, input_ids[audio_idx + 2 :]])
new_labels = None
if labels is not None:
label_seq = labels[audio_idx - 1 : audio_idx + 2]
duplicated_labels = label_seq.repeat(num_chunks)
new_labels = torch.cat([labels[: audio_idx - 1], duplicated_labels, labels[audio_idx + 2 :]])
return new_input_ids, new_labels, num_chunks
def __call__(self, batch: List[ChatMLDatasetSample]):
label_ids = None
label_audio_ids = None
if all([ele.label_ids is None for ele in batch]):
return_labels = False
else:
return_labels = True
processed_batch = batch
# Get the max sequence length based on processed batch
max_seq_length = _ceil_to_nearest(max([len(sample.input_ids) for sample in processed_batch]), self.round_to)
# Get the ids for audio-in and audio-out for each batch
audio_in_ids_l = []
audio_out_ids_l = []
audio_out_ids_group_loc_l = []
audio_in_label_ids_l = None
audio_out_label_ids_l = None
reward_l = []
if return_labels:
audio_out_no_train_flag = [] # Whether the audio-out data should be trained on or not.
# Process the audio inputs and outputs
for i in range(len(processed_batch)):
audio_in_mask = processed_batch[i].input_ids == self.audio_in_token_id
audio_out_mask = processed_batch[i].input_ids == self.audio_out_token_id
audio_ids = torch.ones_like(processed_batch[i].input_ids)
audio_ids[audio_in_mask ^ audio_out_mask] = torch.cumsum(audio_ids[audio_in_mask ^ audio_out_mask], 0) - 1
audio_in_ids = audio_ids[audio_in_mask]
audio_out_ids = audio_ids[audio_out_mask]
if return_labels:
audio_out_no_train_flag.append(processed_batch[i].label_ids[audio_out_mask] < 0)
if self.mask_audio_out_token_label:
processed_batch[i].label_ids[audio_out_mask] = -100
if self.return_audio_in_tokens:
audio_in_ids_l.extend(
[processed_batch[i].get_audio_codes(idx)[: self.audio_num_codebooks, :] for idx in audio_in_ids]
)
if processed_batch[i].audio_label_ids_concat is not None:
if audio_in_label_ids_l is None:
audio_in_label_ids_l = []
audio_in_label_ids_l.extend(
[
processed_batch[i].get_audio_codes_labels(idx)[: self.audio_num_codebooks, :]
for idx in audio_in_ids
]
)
audio_out_ids_l.extend(
[processed_batch[i].get_audio_codes(idx)[: self.audio_num_codebooks, :] for idx in audio_out_ids]
)
audio_out_ids_group_loc_l.append(i)
if processed_batch[i].reward is not None:
reward_l.append(processed_batch[i].reward)
if processed_batch[i].audio_label_ids_concat is not None:
if audio_out_label_ids_l is None:
audio_out_label_ids_l = []
audio_out_label_ids_l.extend(
[
processed_batch[i].get_audio_codes_labels(idx)[: self.audio_num_codebooks, :]
for idx in audio_out_ids
]
)
if return_labels:
audio_out_no_train_flag = torch.cat(audio_out_no_train_flag, dim=0)
if len(audio_in_ids_l) > 0:
# I tried to remove the for-loop in original implementation
# but to do batching with padding caused problem so I turned it into a list compre.
lengths = [seg.shape[1] for seg in audio_in_ids_l]
aug_lengths = [length + 2 for length in lengths]
audio_in_ids_start = torch.cumsum(
torch.tensor([0] + aug_lengths[:-1], dtype=torch.long), dim=0
)
if self.disable_audio_codes_transform:
audio_in_ids = torch.cat(audio_in_ids_l, dim=1).long()
else:
with_tokens = [
torch.cat([
torch.full((seg.shape[0], 1), self.audio_stream_bos_id, dtype=torch.long),
seg,
torch.full((seg.shape[0], 1), self.audio_stream_eos_id, dtype=torch.long),
], dim=1)
for seg in audio_in_ids_l
]
if self.use_delay_pattern:
with_tokens = [
build_delay_pattern_mask(
tok.unsqueeze(0),
bos_token_id=self.audio_stream_bos_id,
pad_token_id=self.audio_stream_eos_id
)[0]
for tok in with_tokens
]
audio_in_ids = torch.cat(with_tokens, dim=1).long()
else:
audio_in_ids = torch.zeros((0, 0), dtype=torch.long)
audio_in_ids_start = torch.zeros(0, dtype=torch.long)
audio_out_ids_start_group_loc = None
if len(audio_out_ids_l) > 0:
new_audio_out_ids_l = []
label_audio_ids_l = []
for idx, ele in enumerate(audio_out_ids_l):
if self.disable_audio_codes_transform:
audio_codes = ele
if return_labels:
label_audio_ids = audio_out_label_ids_l[idx]
else:
audio_codes = torch.cat(
[
torch.full((ele.shape[0], 1), self.audio_stream_bos_id, dtype=torch.long),
ele,
torch.full((ele.shape[0], 1), self.audio_stream_eos_id, dtype=torch.long),
],
dim=1,
)
if return_labels:
label_audio_ids = torch.cat(
[
torch.full((ele.shape[0], 1), -100, dtype=torch.long),
ele,
torch.full((ele.shape[0], 1), self.audio_stream_eos_id, dtype=torch.long),
],
dim=1,
)
if self.use_delay_pattern:
audio_codes = build_delay_pattern_mask(
audio_codes.unsqueeze(0),
bos_token_id=self.audio_stream_bos_id,
pad_token_id=self.audio_stream_eos_id,
)[0].squeeze(0)
if return_labels:
label_audio_ids = build_delay_pattern_mask(
label_audio_ids.unsqueeze(0),
bos_token_id=-100,
pad_token_id=-100,
)[0].squeeze(0)
new_audio_out_ids_l.append(audio_codes)
if return_labels:
if audio_out_no_train_flag[idx]:
label_audio_ids[:] = -100
label_audio_ids_l.append(label_audio_ids)
audio_out_ids = torch.cat(new_audio_out_ids_l, dim=1).long()
if return_labels:
label_audio_ids = torch.cat(label_audio_ids_l, dim=1).long()
audio_out_ids_start = torch.cumsum(
torch.tensor([0] + [audio_codes.shape[1] for audio_codes in new_audio_out_ids_l[:-1]]), dim=0
)
audio_out_ids_start_group_loc = torch.tensor(audio_out_ids_group_loc_l, dtype=torch.long)
else:
audio_out_ids = torch.zeros((0, 0), dtype=torch.long)
audio_out_ids_start = torch.zeros(0, dtype=torch.long)
if return_labels:
label_audio_ids = torch.zeros((0, 0), dtype=torch.long)
reward = torch.tensor(reward_l, dtype=torch.float32)
# cleaner and faster implementation
def pad_sequence(seq, length, pad_value):
if self.pad_left:
padding = (length - len(seq), 0)
else:
padding = (0, length - len(seq))
return F.pad(seq, padding, value=pad_value)
input_ids = torch.stack([
pad_sequence(ele.input_ids, max_seq_length, self.pad_token_id)
for ele in processed_batch
])
if return_labels:
label_ids = torch.stack([
pad_sequence(ele.label_ids, max_seq_length, -100)
for ele in processed_batch
])
attention_mask = torch.stack([
pad_sequence(torch.ones_like(ele.input_ids), max_seq_length, 0)
for ele in processed_batch
])
if not self.return_audio_in_tokens:
audio_in_ids = None
audio_in_ids_start = None
if self.audio_num_codebooks is not None:
if audio_in_ids is not None:
audio_in_ids = audio_in_ids[: self.audio_num_codebooks]
if audio_out_ids is not None:
audio_out_ids = audio_out_ids[: self.audio_num_codebooks]
if label_audio_ids is not None:
label_audio_ids = label_audio_ids[: self.audio_num_codebooks]
return HiggsAudioBatchInput(
input_ids=input_ids,
attention_mask=attention_mask,
audio_out_ids=audio_out_ids,
audio_out_ids_start=audio_out_ids_start,
audio_out_ids_start_group_loc=audio_out_ids_start_group_loc,
audio_in_ids=audio_in_ids,
audio_in_ids_start=audio_in_ids_start,
label_ids=label_ids,
label_audio_ids=label_audio_ids,
reward=reward,
)

View File

@ -0,0 +1,871 @@
import math
import torch
import torch.nn as nn
from typing import Optional
import torch.nn.functional as F
from torch.nn.utils.parametrizations import weight_norm
import torchaudio
import numpy as np
from torch import vmap
from transformers import AutoModel
def WNConv1d(*args, device = None, dtype = None, operations = None, **kwargs):
return weight_norm(operations.Conv1d(*args, **kwargs, device = device, dtype = dtype))
def WNConvTranspose1d(*args, device = None, dtype = None, operations = None, **kwargs):
return weight_norm(operations.ConvTranspose1d(*args, **kwargs, device = device, dtype = dtype))
@torch.jit.script
def snake(x, alpha):
shape = x.shape
x = x.reshape(shape[0], shape[1], -1)
x = x + (alpha + 1e-9).reciprocal() * torch.sin(alpha * x).pow(2)
x = x.reshape(shape)
return x
class Snake1d(nn.Module):
def __init__(self, channels, device = None, dtype = None):
super().__init__()
self.alpha = nn.Parameter(torch.ones(1, channels, 1, device = device, dtype = dtype))
def forward(self, x):
return snake(x, self.alpha)
class DACResidualUnit(nn.Module):
def __init__(self, dim: int = 16, dilation: int = 1, device = None, dtype = None, operations = None):
super().__init__()
pad = ((7 - 1) * dilation) // 2
self.block = nn.Sequential(
Snake1d(dim, device = device, dtype = dtype),
WNConv1d(dim, dim, kernel_size=7, dilation=dilation, padding=pad, device = device, dtype = dtype, operations = operations),
Snake1d(dim, device = device, dtype = dtype),
WNConv1d(dim, dim, kernel_size=1, device = device, dtype = dtype, operations = operations),
)
def forward(self, x):
y = self.block(x)
pad = (x.shape[-1] - y.shape[-1]) // 2
if pad > 0:
x = x[..., pad:-pad]
return x + y
class DACEncoderBlock(nn.Module):
def __init__(self, dim: int = 16, stride: int = 1, device = None, dtype = None, operations = None):
super().__init__()
self.block = nn.Sequential(
DACResidualUnit(dim // 2, dilation=1, device = device, dtype = dtype, operations = operations),
DACResidualUnit(dim // 2, dilation=3, device = device, dtype = dtype, operations = operations),
DACResidualUnit(dim // 2, dilation=9, device = device, dtype = dtype, operations = operations),
Snake1d(dim // 2),
WNConv1d(
dim // 2,
dim,
kernel_size=2 * stride,
stride=stride,
padding=math.ceil(stride / 2),
device = device, dtype = dtype, operations = operations
),
)
def forward(self, x):
return self.block(x)
class DACEncoder(nn.Module):
def __init__(
self,
d_model: int = 64,
strides: list = [2, 4, 8, 8],
d_latent: int = 256,
device = None, dtype = None, operations = None
):
super().__init__()
# Create first convolution
self.block = [WNConv1d(1, d_model, kernel_size=7, padding=3, device = device, dtype = dtype, operations = operations)]
# Create EncoderBlocks that double channels as they downsample by `stride`
for stride in strides:
d_model *= 2
self.block += [DACEncoderBlock(d_model, stride=stride, device = device, dtype = dtype, operations = operations)]
# Create last convolution
self.block += [
Snake1d(d_model),
WNConv1d(d_model, d_latent, kernel_size=3, padding=1, device = device, dtype = dtype, operations = operations),
]
# Wrap black into nn.Sequential
self.block = nn.Sequential(*self.block)
self.enc_dim = d_model
def forward(self, x):
return self.block(x)
class DACDecoderBlock(nn.Module):
def __init__(self, input_dim: int = 16, output_dim: int = 8, stride: int = 1, device = None, dtype = None, operations = None):
super().__init__()
self.block = nn.Sequential(
Snake1d(input_dim, device = device, dtype = dtype),
WNConvTranspose1d(
input_dim,
output_dim,
kernel_size=2 * stride,
stride=stride,
padding=math.ceil(stride / 2),
output_padding=stride % 2, # out_pad,
device = device, dtype = dtype, operations = operations
),
DACResidualUnit(output_dim, dilation=1, device = device, dtype = dtype, operations = operations),
DACResidualUnit(output_dim, dilation=3, device = device, dtype = dtype, operations = operations),
DACResidualUnit(output_dim, dilation=9, device = device, dtype = dtype, operations = operations),
)
def forward(self, x):
return self.block(x)
class DACDecoder(nn.Module):
def __init__(
self,
input_channel,
channels,
rates,
d_out: int = 1,
device = None, dtype = None, operations = None
):
super().__init__()
# Add first conv layer
layers = [WNConv1d(input_channel, channels, kernel_size=7, padding=3, device = device, dtype = dtype, operations = operations )]
# Add upsampling + MRF blocks
for i, stride in enumerate(rates):
input_dim = channels // 2**i
output_dim = channels // 2 ** (i + 1)
layers += [DACDecoderBlock(input_dim, output_dim, stride, device = device, dtype = dtype, operations = operations)]
# Add final conv layer
layers += [
Snake1d(output_dim, device = device, dtype = dtype),
WNConv1d(output_dim, d_out, kernel_size=7, padding=3, device = device, dtype = dtype, operations = operations),
]
self.model = nn.Sequential(*layers)
def forward(self, x):
return self.model(x)
class Conv1d1x1:
def __new__(cls, in_channels, out_channels, bias=True, device=None, dtype=None, operations=None):
operations = operations or nn
return operations.Conv1d(
in_channels, out_channels, kernel_size=1,
bias=bias, device=device, dtype=dtype
)
class Conv1d(nn.Module):
def __init__(
self,
in_channels: int,
out_channels: int,
kernel_size: int,
stride: int = 1,
padding: int = -1,
dilation: int = 1,
groups: int = 1,
bias: bool = True,
device = None, dtype = None, operations = None
):
super().__init__()
if padding < 0:
padding = (kernel_size - 1) // 2 * dilation
self.dilation = dilation
self.conv = operations.Conv1d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
dilation=dilation,
groups=groups,
bias=bias,
device = device, dtype = dtype
)
def forward(self, x):
x = self.conv(x)
return x
class ConvTranspose1d(nn.Module):
def __init__(
self,
in_channels: int,
out_channels: int,
kernel_size: int,
stride: int,
padding=-1,
output_padding=-1,
groups=1,
bias=True,
device = None, dtype = None, operations = None
):
super().__init__()
if padding < 0:
padding = (stride + 1) // 2
if output_padding < 0:
output_padding = 1 if stride % 2 else 0
self.deconv = operations.ConvTranspose1d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
output_padding=output_padding,
groups=groups,
bias=bias,
device = device, dtype = dtype
)
def forward(self, x):
x = self.deconv(x)
return x
class ResidualUnit(nn.Module):
def __init__(
self,
in_channels: int,
out_channels: int,
kernel_size=3,
dilation=1,
bias=False,
nonlinear_activation="ELU",
nonlinear_activation_params={},
device = None, dtype = None, operations = None
):
super().__init__()
self.activation = getattr(nn, nonlinear_activation)(**nonlinear_activation_params)
self.conv1 = Conv1d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=kernel_size,
stride=1,
dilation=dilation,
bias=bias,
device = device, dtype = dtype, operations = operations
)
self.conv2 = Conv1d1x1(out_channels, out_channels, bias, device = device, dtype = dtype, operations = operations)
def forward(self, x):
y = self.conv1(self.activation(x))
y = self.conv2(self.activation(y))
return x + y
class EncoderBlock(nn.Module):
def __init__(
self, in_channels: int, out_channels: int, stride: int, dilations=(1, 1), unit_kernel_size=3, bias=True, device = None, dtype = None, operations = None
):
super().__init__()
self.res_units = torch.nn.ModuleList()
for dilation in dilations:
self.res_units += [ResidualUnit(in_channels, in_channels, kernel_size=unit_kernel_size, dilation=dilation, device = device, dtype = dtype, operations = operations)]
self.num_res = len(self.res_units)
kernel_size=3 if stride == 1 else (2 * stride) # special case: stride=1, do not use kernel=2
self.conv = Conv1d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size = kernel_size,
stride=stride,
bias=bias,
device = device, dtype = dtype, operations = operations
)
def forward(self, x):
for idx in range(self.num_res):
x = self.res_units[idx](x)
x = self.conv(x)
return x
class Encoder(nn.Module):
def __init__(
self,
input_channels: int,
encode_channels: int,
channel_ratios=(1, 1),
strides=(1, 1),
kernel_size=3,
bias=True,
block_dilations=(1, 1),
unit_kernel_size=3,
device = None, dtype = None, operations = None
):
super().__init__()
assert len(channel_ratios) == len(strides)
self.conv = Conv1d(
in_channels=input_channels, out_channels=encode_channels, kernel_size=kernel_size, stride=1, bias=False,
device = device, dtype = dtype, operations = operations
)
self.conv_blocks = torch.nn.ModuleList()
in_channels = encode_channels
for idx, stride in enumerate(strides):
out_channels = int(encode_channels * channel_ratios[idx]) # could be float
self.conv_blocks += [
EncoderBlock(
in_channels,
out_channels,
stride,
dilations=block_dilations,
unit_kernel_size=unit_kernel_size,
bias=bias,
device = device, dtype = dtype, operations = operations
)
]
in_channels = out_channels
self.num_blocks = len(self.conv_blocks)
self.out_channels = out_channels
def forward(self, x):
x = self.conv(x)
for i in range(self.num_blocks):
x = self.conv_blocks[i](x)
return x
class DecoderBlock(nn.Module):
"""Decoder block (no up-sampling)"""
def __init__(
self, in_channels: int, out_channels: int, stride: int, dilations=(1, 1), unit_kernel_size=3, bias=True, device = None, dtype = None, operations = None
):
super().__init__()
if stride == 1:
self.conv = Conv1d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=3, # fix kernel=3 when stride=1 for unchanged shape
stride=stride,
bias=bias,
device = device, dtype = dtype, operations = operations
)
else:
self.conv = ConvTranspose1d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=(2 * stride),
stride=stride,
bias=bias,
device = device, dtype = dtype, operations = operations
)
self.res_units = nn.ModuleList([
ResidualUnit(out_channels, out_channels, kernel_size=unit_kernel_size, dilation=d, device = device, dtype = dtype, operations = operations)
for d in dilations
])
self.num_res = len(self.res_units)
def forward(self, x):
x = self.conv(x)
for idx in range(self.num_res):
x = self.res_units[idx](x)
return x
class Decoder(nn.Module):
def __init__(
self,
code_dim: int,
output_channels: int,
decode_channels: int,
channel_ratios=(1, 1),
strides=(1, 1),
kernel_size=3,
bias=True,
block_dilations=(1, 1),
unit_kernel_size=3,
device = None, dtype = None, operations = None
):
super().__init__()
assert len(channel_ratios) == len(strides)
self.conv1 = Conv1d(
in_channels=code_dim,
out_channels=int(decode_channels * channel_ratios[0]),
kernel_size=kernel_size,
stride=1,
bias=False,
device = device, dtype = dtype, operations = operations
)
self.conv_blocks = torch.nn.ModuleList()
for idx, stride in enumerate(strides):
in_channels = int(decode_channels * channel_ratios[idx])
if idx < (len(channel_ratios) - 1):
out_channels = int(decode_channels * channel_ratios[idx + 1])
else:
out_channels = decode_channels
self.conv_blocks += [
DecoderBlock(
in_channels,
out_channels,
stride,
dilations=block_dilations,
unit_kernel_size=unit_kernel_size,
bias=bias,
device = device, dtype = dtype, operations = operations
)
]
self.num_blocks = len(self.conv_blocks)
self.conv2 = Conv1d(out_channels, output_channels, kernel_size = 3, bias=False, device = device, dtype = dtype, operations = operations)
def forward(self, z):
x = self.conv1(z)
for i in range(self.num_blocks):
x = self.conv_blocks[i](x)
x = self.conv2(x)
return x
class HiggsAudioFeatureExtractor(nn.Module):
def __init__(self, sampling_rate=16000):
super().__init__()
self.sampling_rate = sampling_rate
def forward(self, audio_signal):
audio_signal = audio_signal.unsqueeze(0)
if len(audio_signal.shape) < 3:
audio_signal = audio_signal.unsqueeze(0)
return {"input_values": audio_signal}
def uniform_init(*shape: int, device = None, dtype = None):
t = torch.empty(shape, device = device, dtype = dtype)
nn.init.kaiming_uniform_(t)
return t
class EuclideanCodebook(nn.Module):
def __init__(
self,
dim: int,
codebook_size: int,
kmeans_init: int = False,
kmeans_iters: int = 10,
decay: float = 0.99,
epsilon: float = 1e-5,
threshold_ema_dead_code: int = 2,
device = None, dtype = None
):
super().__init__()
self.decay = decay
init_fn = uniform_init
embed = init_fn(codebook_size, dim, device = device, dtype = dtype)
self.codebook_size = codebook_size
self.kmeans_iters = kmeans_iters
self.epsilon = epsilon
self.threshold_ema_dead_code = threshold_ema_dead_code
# Flag variable to indicate whether the codebook is initialized
self.register_buffer("inited", torch.Tensor([not kmeans_init]))
# Runing EMA cluster size/count: N_i^t in eq. (6) in vqvae paper
self.register_buffer("cluster_size", torch.zeros(codebook_size))
# Codebook
self.register_buffer("embed", embed)
# EMA codebook: eq. (7) in vqvae paper
self.register_buffer("embed_avg", embed.clone())
def preprocess(self, x):
x = x.view(-1, x.shape[-1])
return x
def quantize(self, x):
embed = self.embed.t()
if x.dtype != embed.dtype:
x = x.to(embed.dtype)
dist = -(x.pow(2).sum(1, keepdim=True) - 2 * x @ embed + embed.pow(2).sum(0, keepdim=True))
embed_ind = dist.max(dim=-1).indices
return embed_ind
def postprocess_emb(self, embed_ind, shape):
return embed_ind.view(*shape[:-1])
def dequantize(self, embed_ind):
quantize = F.embedding(embed_ind, self.embed)
return quantize
def encode(self, x):
shape = x.shape
# pre-process
x = self.preprocess(x) # [B, T, D] -> [B*T, D]
# quantize
embed_ind = self.quantize(x)
# post-process
embed_ind = self.postprocess_emb(embed_ind, shape)
return embed_ind
def decode(self, embed_ind):
quantize = self.dequantize(embed_ind)
return quantize
def forward(self, x):
orig_shape = x.shape # [B, T, D]
flat = x.view(-1, x.shape[-1]) # [B*T, D]
embed_ind = self.quantize(flat)
embed_ind = self.postprocess_emb(embed_ind, orig_shape)
# now embed_ind has shape [B, T]
quantize = self.dequantize(embed_ind)
# quantize: [B, T, D]
return quantize, embed_ind
class VectorQuantization(nn.Module):
def __init__(
self,
dim: int,
codebook_size: int,
codebook_dim: Optional[int] = None,
decay: float = 0.99,
epsilon: float = 1e-5,
kmeans_init: bool = True,
kmeans_iters: int = 50,
threshold_ema_dead_code: int = 2,
commitment_weight: float = 1.0,
device = None, dtype = None, operations = None
):
super().__init__()
_codebook_dim: int = codebook_dim if codebook_dim is not None else dim
requires_projection = _codebook_dim != dim
self.project_in = operations.Linear(dim, _codebook_dim, device = device, dtype = dtype) if requires_projection else nn.Identity()
self.project_out = operations.Linear(_codebook_dim, dim, device = device, dtype = dtype) if requires_projection else nn.Identity()
self.epsilon = epsilon
self.commitment_weight = commitment_weight
self._codebook = EuclideanCodebook(
dim=_codebook_dim,
codebook_size=codebook_size,
kmeans_init=kmeans_init,
kmeans_iters=kmeans_iters,
decay=decay,
epsilon=epsilon,
threshold_ema_dead_code=threshold_ema_dead_code,
device = device, dtype = dtype
)
self.codebook_size = codebook_size
@property
def codebook(self):
return self._codebook.embed
def encode(self, x):
x = x.permute(0, 2, 1)
x = self.project_in(x)
embed_in = self._codebook.encode(x)
return embed_in
def decode(self, embed_ind):
quantize = self._codebook.decode(embed_ind)
quantize = self.project_out(quantize)
quantize = quantize.permute(0, 2, 1)
return quantize
def forward(self, x):
device = x.device
x = x.transpose(1, 2).contiguous() # [b d n] -> [b n d]
x = self.project_in(x)
quantize, embed_ind = self._codebook(x)
loss = torch.tensor([0.0], device=device, requires_grad=self.training)
quantize = self.project_out(quantize)
quantize = quantize.transpose(1, 2).contiguous() # [b n d] -> [b d n]
return quantize, embed_ind, loss
class ResidualVectorQuantization(nn.Module):
def __init__(self, *, num_quantizers, device = None, dtype = None, operations = None, **kwargs):
super().__init__()
self.layers = nn.ModuleList([VectorQuantization(device = device, dtype = dtype, operations = operations, **kwargs) for _ in range(num_quantizers)])
def forward(self, x, n_q: Optional[int] = None):
quantized_out = 0.0
residual = x
all_losses = []
all_indices = []
n_q = n_q or len(self.layers)
for layer in self.layers[:n_q]:
quantized, indices, loss = layer(residual)
residual = residual - quantized
quantized_out = quantized_out + quantized
all_indices.append(indices)
all_losses.append(loss)
out_losses, out_indices = map(torch.stack, (all_losses, all_indices))
return quantized_out, out_indices, out_losses
def decode(self, q_indices: torch.Tensor) -> torch.Tensor:
""" Vectorized Implementation of dequantization | 2x faster than original impl """
biases = torch.stack([layer.project_out.bias for layer in self.layers])
codebook_device = self.layers[0]._codebook.embed.device
q_indices = q_indices.to(codebook_device)
def decode_one(codebook_weight, proj_weight, embed_id, proj_biases):
quantized = F.embedding(embed_id, codebook_weight).transpose(1, 2) # (B, D, T)
quantized = F.linear(quantized.transpose(1, 2), proj_weight, proj_biases).transpose(1, 2)
return quantized
codebook_weights = torch.stack([q._codebook.embed for q in self.layers]) # (n_codebooks, vocab_size, D)
proj_weights = torch.stack([q.project_out.weight for q in self.layers])
quantized = vmap(decode_one)(codebook_weights, proj_weights, q_indices, biases)
return quantized.sum(0)
class ResidualVectorQuantizer(nn.Module):
def __init__(
self,
dimension: int = 256,
codebook_dim: int = None,
n_q: int = 8,
bins: int = 1024,
decay: float = 0.99,
kmeans_init: bool = True,
kmeans_iters: int = 50,
threshold_ema_dead_code: int = 2,
device = None,
dtype = None,
operations = None
):
super().__init__()
self.n_q = n_q
self.dimension = dimension
self.codebook_dim = codebook_dim
self.bins = bins
self.decay = decay
self.kmeans_init = kmeans_init
self.kmeans_iters = kmeans_iters
self.threshold_ema_dead_code = threshold_ema_dead_code
self.vq = ResidualVectorQuantization(
dim=self.dimension,
codebook_dim=self.codebook_dim,
codebook_size=self.bins,
num_quantizers=self.n_q,
decay=self.decay,
kmeans_init=self.kmeans_init,
kmeans_iters=self.kmeans_iters,
threshold_ema_dead_code=self.threshold_ema_dead_code,
device = device, dtype = dtype, operations = operations
)
def forward(self, x: torch.Tensor, sample_rate: int, bandwidth: Optional[float] = None): # -> QuantizedResult:
bw_per_q = self.get_bandwidth_per_quantizer(sample_rate)
n_q = self.get_num_quantizers_for_bandwidth(sample_rate, bandwidth)
quantized, codes, commit_loss = self.vq(x, n_q=n_q)
bw = torch.tensor(n_q * bw_per_q).to(x)
return quantized, codes, bw, torch.mean(commit_loss)
def get_num_quantizers_for_bandwidth(self, sample_rate: int, bandwidth: Optional[float] = None) -> int:
"""Return n_q based on specified target bandwidth."""
bw_per_q = self.get_bandwidth_per_quantizer(sample_rate)
n_q = self.n_q
if bandwidth and bandwidth > 0.0:
n_q = int(max(1, math.floor(bandwidth / bw_per_q)))
return n_q
def get_bandwidth_per_quantizer(self, sample_rate: int):
"""Return bandwidth per quantizer for a given input sample rate."""
return math.log2(self.bins) * sample_rate / 1000
def decode(self, codes: torch.Tensor) -> torch.Tensor:
"""Decode the given codes to the quantized representation."""
quantized = self.vq.decode(codes)
return quantized
class HiggsAudioTokenizer(nn.Module):
def __init__(
self,
D: int = 256,
target_bandwidths= [0.5, 1, 1.5, 2, 4],
ratios = [8, 5, 4, 2, 3], # downsampling by 320
sample_rate: int = 24000,
bins: int = 1024,
n_q: int = 8,
codebook_dim: int = 64,
last_layer_semantic: bool = True,
downsample_mode: str = "step_down",
vq_scale: int = 1,
semantic_sample_rate: int = None,
device = None,
dtype = None,
operations = None,
**kwargs
):
super().__init__()
operations = operations or nn
self.hop_length = np.prod(ratios)
self.frame_rate = math.ceil(sample_rate / np.prod(ratios)) # 50 Hz
self.target_bandwidths = target_bandwidths
self.n_q = n_q
self.sample_rate = sample_rate
self.encoder = DACEncoder(64, ratios, D, device = device, dtype = dtype, operations = operations)
self.decoder_2 = DACDecoder(D, 1024, ratios, device = device, dtype = dtype, operations = operations)
self.last_layer_semantic = last_layer_semantic
self.device = device
self.semantic_model = AutoModel.from_pretrained("bosonai/hubert_base", trust_remote_code=True)
self.semantic_sample_rate = 16000
self.semantic_dim = 768
self.encoder_semantic_dim = 768
# Overwrite semantic model sr to ensure semantic_downsample_factor is an integer
if semantic_sample_rate is not None:
self.semantic_sample_rate = semantic_sample_rate
self.semantic_model.eval()
# make the semantic model parameters do not need gradient
for param in self.semantic_model.parameters():
param.requires_grad = False
self.semantic_downsample_factor = int(self.hop_length / (self.sample_rate / self.semantic_sample_rate) / 320)
self.quantizer_dim = int((D + self.encoder_semantic_dim) // vq_scale)
self.encoder_semantic = Encoder(input_channels=self.semantic_dim, encode_channels=self.encoder_semantic_dim, device = device, dtype = dtype, operations = operations)
self.decoder_semantic = Decoder(
code_dim=self.encoder_semantic_dim, output_channels=self.semantic_dim, decode_channels=self.semantic_dim, device = device, dtype = dtype, operations = operations
)
self.quantizer = ResidualVectorQuantizer(
dimension=self.quantizer_dim, codebook_dim=codebook_dim, n_q=n_q, bins=bins, device = device, dtype = dtype, operations = operations
)
self.fc_prior = operations.Linear(D + self.encoder_semantic_dim, self.quantizer_dim, device = device, dtype = dtype)
self.fc_post1 = operations.Linear(self.quantizer_dim, self.encoder_semantic_dim, device = device, dtype = dtype)
self.fc_post2 = operations.Linear(self.quantizer_dim, D, device = device, dtype = dtype)
self.downsample_mode = downsample_mode
self.audio_tokenizer_feature_extractor = HiggsAudioFeatureExtractor(sampling_rate=self.sample_rate)
@property
def sampling_rate(self):
return self.sample_rate
@torch.no_grad()
def get_regress_target(self, x):
x = torchaudio.functional.resample(x, self.sample_rate, self.semantic_sample_rate)
x = x[:, 0, :]
x = F.pad(x, (160, 160))
target = self.semantic_model(x, output_hidden_states=True).hidden_states
target = torch.stack(target, dim=1)
target = target.mean(1)
if self.downsample_mode == "step_down":
if self.semantic_downsample_factor > 1:
target = target[:, :: self.semantic_downsample_factor, :]
return target
def forward(self):
pass
@property
def tps(self):
return self.frame_rate
def encode(self, wv, sr):
if sr != self.sampling_rate:
# best computed values to match librosa's resample
resampler_torch = torchaudio.transforms.Resample(
orig_freq=sr,
new_freq=self.sampling_rate,
resampling_method="sinc_interp_kaiser",
lowpass_filter_width = 121,
rolloff = 0.9568384289091556,
beta = 21.01531462440614
).to(wv.device)
wv = resampler_torch(wv)
if self.audio_tokenizer_feature_extractor is not None:
inputs = self.audio_tokenizer_feature_extractor(wv)
input_values = inputs["input_values"].to(self.device)
else:
input_values = torch.from_numpy(wv).float().unsqueeze(0)
with torch.no_grad():
input_values = input_values.to(wv.device)
encoder_outputs = self._xcodec_encode(input_values)
vq_code = encoder_outputs[0]
return vq_code
def _xcodec_encode(self, x: torch.Tensor, target_bw: Optional[int] = None) -> torch.Tensor:
bw = target_bw
e_semantic_input = self.get_regress_target(x).detach()
e_semantic = self.encoder_semantic(e_semantic_input.transpose(1, 2))
e_acoustic = self.encoder(x)
if e_acoustic.shape[2] != e_semantic.shape[2]:
pad_size = 160 * self.semantic_downsample_factor
e_acoustic = self.encoder(F.pad(x[:, 0, :], (pad_size, pad_size)).unsqueeze(0))
if e_acoustic.shape[2] != e_semantic.shape[2]:
if e_acoustic.shape[2] > e_semantic.shape[2]:
e_acoustic = e_acoustic[:, :, : e_semantic.shape[2]]
else:
e_semantic = e_semantic[:, :, : e_acoustic.shape[2]]
e = torch.cat([e_acoustic, e_semantic], dim=1)
e = self.fc_prior(e.transpose(1, 2))
e = e.transpose(1, 2)
_, codes, _, _ = self.quantizer(e, self.frame_rate, bw)
codes = codes.permute(1, 0, 2)
return codes
def decode(self, vq_code: torch.Tensor) -> torch.Tensor:
vq_code = vq_code.to(self.device)
if vq_code.ndim < 3:
vq_code = vq_code.unsqueeze(0)
vq_code = vq_code.permute(1, 0, 2)
quantized = self.quantizer.decode(vq_code)
quantized = quantized.transpose(1, 2)
quantized_acoustic = self.fc_post2(quantized).transpose(1, 2)
o = self.decoder_2(quantized_acoustic)
return o.detach()

View File

@ -46,6 +46,7 @@ import comfy.ldm.chroma.model
import comfy.ldm.chroma_radiance.model
import comfy.ldm.ace.model
import comfy.ldm.omnigen.omnigen2
import comfy.ldm.higgsv2.model
import comfy.ldm.qwen_image.model
import comfy.model_management
@ -1471,6 +1472,10 @@ class Omnigen2(BaseModel):
out['ref_latents'] = list([1, 16, sum(map(lambda a: math.prod(a.size()), ref_latents)) // 16])
return out
class Higgsv2(BaseModel):
def __init__(self, model_config, model_type=ModelType.EPS, device=None, unet_model=comfy.ldm.higgsv2.model.HiggsAudioModel):
super().__init__(model_config, model_type, device, unet_model)
class QwenImage(BaseModel):
def __init__(self, model_config, model_type=ModelType.FLUX, device=None):
super().__init__(model_config, model_type, device=device, unet_model=comfy.ldm.qwen_image.model.QwenImageTransformer2DModel)

View File

@ -527,6 +527,73 @@ def detect_unet_config(state_dict, key_prefix, metadata=None):
dit_config["guidance_cond_proj_dim"] = None#f"{key_prefix}t_embedder.cond_proj.weight" in state_dict_keys
return dit_config
if "{}layers.27.audio_post_attention_layernorm.weight".format(key_prefix) in state_dict_keys:
autoregressive_config = {}
autoregressive_config["image_model"] = "higgsv2"
autoregressive_config["audio_adapter_type"] = "dual_ffn_fast_forward"
autoregressive_config["audio_bos_token"] = "<|audio_bos|>"
autoregressive_config["audio_codebook_size"] = 1024
autoregressive_config["audio_num_codebooks"] = 8
autoregressive_config["audio_ffn_hidden_size"] = 3072
autoregressive_config["audio_ffn_intermediate_size"] = 8192
autoregressive_config["audio_in_token"] = "<|AUDIO|>"
autoregressive_config["audio_in_token_idx"] = 128015
autoregressive_config["audio_out_token"] = "<|AUDIO_OUT|>"
autoregressive_config["audio_out_token_idx"] = 128016
autoregressive_config["audio_out_bos_token"] = "<|audio_out_bos|>"
autoregressive_config["audio_out_bos_token_id"] = 128013
autoregressive_config["audio_eos_token"] = "<|audio_eos|>"
autoregressive_config["audio_eos_token_id"] = 128012
autoregressive_config["audio_stream_bos_id"] = 1024
autoregressive_config["audio_stream_eos_id"] = 1025
autoregressive_config["encode_audio_in_tokens"] = True
autoregressive_config["pad_token_id"] = 128001
autoregressive_config["padding_idx"] = 128001
autoregressive_config["hidden_size"] = 3072
autoregressive_config["use_delay_pattern"] = True
autoregressive_config["vocab_size"] = 128256
autoregressive_config["num_hidden_layers"] = 28
autoregressive_config["num_attention_heads"] = 24
autoregressive_config["num_key_value_heads"] = 8
autoregressive_config["max_seq_len"] = 131072
autoregressive_config["max_position_embeddings"] = 131072
autoregressive_config["bos_token_id"] = 128000
autoregressive_config["eos_token_id"] = 128001
autoregressive_config["use_cache"] = True
autoregressive_config["text_config"] = {
"model_type": "llama",
"vocab_size": 128256,
"max_position_embeddings": 131072,
"num_hidden_layers": 28,
"hidden_size": 3072,
"num_attention_heads": 24,
"num_key_value_heads": 8,
"initializer_range": 0.02,
"rms_norm_eps": 1e-05,
"pad_token_id": None,
"bos_token_id": 128000,
"eos_token_id": 128001,
"num_return_sequences": 1,
"head_dim": 128,
"mlp_bias": False,
"intermediate_size": 8192
}
autoregressive_config["use_kv_buckets"] = True
autoregressive_config["num_attention_heads"] = 24
autoregressive_config["audio_decoder_proj_num_layers"] = 0
autoregressive_config["audio_dual_ffn_layers"] = list(range(28))
autoregressive_config["output_vae"] = False
return autoregressive_config
if '{}caption_projection.0.linear.weight'.format(key_prefix) in state_dict_keys: # HiDream
dit_config = {}
dit_config["image_model"] = "hidream"

View File

@ -1312,6 +1312,7 @@ def load_state_dict_guess_config(sd, output_vae=True, output_clip=True, output_c
manual_cast_dtype = model_management.unet_manual_cast(unet_dtype, load_device, model_config.supported_inference_dtypes)
model_config.set_inference_dtype(unet_dtype, manual_cast_dtype)
output_vae = model_config.unet_config.get("output_vae", output_vae)
if model_config.clip_vision_prefix is not None:
if output_clipvision:

View File

@ -19,6 +19,7 @@ import comfy.text_encoders.lumina2
import comfy.text_encoders.wan
import comfy.text_encoders.ace
import comfy.text_encoders.omnigen2
import comfy.text_encoders.higgsv2
import comfy.text_encoders.qwen_image
import comfy.text_encoders.hunyuan_image
import comfy.text_encoders.z_image
@ -1384,6 +1385,22 @@ class QwenImage(supported_models_base.BASE):
hunyuan_detect = comfy.text_encoders.hunyuan_video.llama_detect(state_dict, "{}qwen25_7b.transformer.".format(pref))
return supported_models_base.ClipTarget(comfy.text_encoders.qwen_image.QwenImageTokenizer, comfy.text_encoders.qwen_image.te(**hunyuan_detect))
class Higgsv2(supported_models_base.BASE):
unet_config = {
"image_model": "higgsv2",
}
memory_usage_factor = 1.0
supported_inference_dtypes = [torch.float16, torch.bfloat16, torch.float32]
text_encoder_key_prefix = ["dac."]
def get_model(self, state_dict, prefix="", device=None):
out = model_base.Higgsv2(self, device=device)
return out
def clip_target(self, state_dict = {}):
return supported_models_base.ClipTarget(comfy.text_encoders.higgsv2.DummyTokenizer, comfy.text_encoders.higgsv2.HiggsTokenizer)
class HunyuanImage21(HunyuanVideo):
unet_config = {
"image_model": "hunyuan_video",
@ -1474,7 +1491,6 @@ class HunyuanVideo15_SR_Distilled(HunyuanVideo):
hunyuan_detect = comfy.text_encoders.hunyuan_video.llama_detect(state_dict, "{}qwen25_7b.transformer.".format(pref))
return supported_models_base.ClipTarget(comfy.text_encoders.hunyuan_video.HunyuanVideo15Tokenizer, comfy.text_encoders.hunyuan_image.te(**hunyuan_detect))
models = [LotusD, Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p, SD3, StableAudio, AuraFlow, PixArtAlpha, PixArtSigma, HunyuanDiT, HunyuanDiT1, FluxInpaint, Flux, FluxSchnell, GenmoMochi, LTXV, HunyuanVideo15_SR_Distilled, HunyuanVideo15, HunyuanImage21Refiner, HunyuanImage21, HunyuanVideoSkyreelsI2V, HunyuanVideoI2V, HunyuanVideo, CosmosT2V, CosmosI2V, CosmosT2IPredict2, CosmosI2VPredict2, ZImage, Lumina2, WAN22_T2V, WAN21_T2V, WAN21_I2V, WAN21_FunControl2V, WAN21_Vace, WAN21_Camera, WAN22_Camera, WAN22_S2V, WAN21_HuMo, WAN22_Animate, Hunyuan3Dv2mini, Hunyuan3Dv2, Hunyuan3Dv2_1, HiDream, Chroma, ChromaRadiance, ACEStep, Omnigen2, QwenImage, Flux2]
models = [LotusD, Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p, SD3, StableAudio, AuraFlow, PixArtAlpha, PixArtSigma, HunyuanDiT, HunyuanDiT1, FluxInpaint, Flux, FluxSchnell, GenmoMochi, LTXV, HunyuanVideo15_SR_Distilled, HunyuanVideo15, HunyuanImage21Refiner, HunyuanImage21, HunyuanVideoSkyreelsI2V, HunyuanVideoI2V, HunyuanVideo, CosmosT2V, CosmosI2V, CosmosT2IPredict2, CosmosI2VPredict2, ZImage, Lumina2, WAN22_T2V, WAN21_T2V, WAN21_I2V, WAN21_FunControl2V, WAN21_Vace, WAN21_Camera, WAN22_Camera, WAN22_S2V, WAN21_HuMo, WAN22_Animate, Hunyuan3Dv2mini, Hunyuan3Dv2, Hunyuan3Dv2_1, HiDream, Chroma, ChromaRadiance, ACEStep, Omnigen2, QwenImage, Flux2, Higgsv2]
models += [SVD_img2vid]

View File

@ -0,0 +1,16 @@
{
"bos_token": {
"content": "<|begin_of_text|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
},
"eos_token": {
"content": "<|end_of_text|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,81 @@
import os
import torch
import comfy.ops
import torch.nn as nn
from transformers import AutoTokenizer
from comfy.ldm.higgsv2.tokenizer import HiggsAudioTokenizer
from comfy.ldm.higgsv2.preprocess import HiggsAudioSampleCollator
class DummyTokenizer:
def __init__(self, embedding_directory=None, tokenizer_data={}):
pass
def revert_delay_pattern_vectorized(data: torch.Tensor) -> torch.Tensor:
num_codebooks, total_len = data.shape
seq_len = total_len - num_codebooks + 1
col_idx = torch.arange(seq_len, device=data.device)[None, :] \
+ torch.arange(num_codebooks, device=data.device)[:, None]
out = data[torch.arange(num_codebooks)[:, None], col_idx]
return out
class HiggsTokenizer(nn.Module):
def __init__(self, device, dtype, model_options={}, **kwargs):
super().__init__()
self.dtype = torch.float32
self.device = device
self.dtypes = [torch.float32]
here = os.path.dirname(__file__)
tokenizer_path = os.path.join(here, "higgs_text_tokenizer")
self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path)
scaled_fp8 = model_options.get("scaled_fp8", None)
if scaled_fp8 is not None:
operations = comfy.ops.scaled_fp8_ops(fp8_matrix_mult=False, override_dtype=scaled_fp8)
else:
operations = comfy.ops.manual_cast
self.audio_codebook_size = 1024
self.audio_tokenizer = HiggsAudioTokenizer(device = device, dtype = dtype, operations = operations)
if scaled_fp8 is not None:
self.audio_tokenizer.scaled_fp8 = torch.nn.Parameter(torch.tensor([], dtype=scaled_fp8))
self.collator = HiggsAudioSampleCollator(
audio_in_token_id = 128015,
audio_out_token_id = 128016,
audio_stream_bos_id = 1024,
audio_stream_eos_id = 1025,
pad_token_id = 128001,
return_audio_in_tokens = False,
use_delay_pattern = True,
audio_num_codebooks = 8,
round_to = 1,
)
postfix = "<|start_header_id|>assistant<|end_header_id|>\n\n"
self.postfix = postfix + "<|audio_out_bos|>" # force audio generation
def decode_tokens(self, audio_tokens):
outputs = []
# due to instability issues, I had to convert the audio tokenizer to float32, avoiding outputing nans
self.audio_tokenizer = self.audio_tokenizer.to(self.dtype)
torch.cuda.synchronize()
for audio in audio_tokens:
vq_code = revert_delay_pattern_vectorized(audio).clip(0, self.audio_codebook_size - 1)[:, 1:-1]
wv_numpy = self.audio_tokenizer.decode(vq_code.unsqueeze(0))[0, 0]
outputs.append(wv_numpy)
# currently only supports one batch size
return (None, {"waveform": torch.cat(outputs, dim = 0).unsqueeze(0).unsqueeze(1), "sample_rate": self.audio_tokenizer.sample_rate}) # audio only
def load_state_dict(self, sd, strict = False):
return self.audio_tokenizer.load_state_dict(sd, strict = strict)
def state_dict(self):
return self.audio_tokenizer.state_dict()

View File

@ -1,12 +1,13 @@
import torch
import torch.nn as nn
from dataclasses import dataclass
from typing import Optional, Any
from dataclasses import dataclass, field
from transformers.cache_utils import Cache
from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS
import math
import logging
from comfy.ldm.modules.attention import optimized_attention_for_device
import comfy.model_management
import comfy.ldm.common_dit
import comfy.model_management
@ -24,9 +25,20 @@ class Llama2Config:
rms_norm_eps: float = 1e-5
rope_theta: float = 500000.0
transformer_type: str = "llama"
head_dim = 128
head_dim: int = 128
rms_norm_add = False
mlp_activation = "silu"
qkv_bias: bool = False
rope_type: str = "llama3"
rope_scaling: dict = field(
default_factory=lambda: {
"factor": 32.0,
"high_freq_factor": 4.0,
"low_freq_factor": 1.0,
"original_max_position_embeddings": 8192,
"rope_type": "llama3"
}
)
qkv_bias = False
rope_dims = None
q_norm = None
@ -255,15 +267,67 @@ def apply_rope(xq, xk, freqs_cis):
sin = freqs_cis[1]
q_embed = (xq * cos) + (rotate_half(xq) * sin)
k_embed = (xk * cos) + (rotate_half(xk) * sin)
return q_embed.to(org_dtype), k_embed.to(org_dtype)
return q_embed.to(org_dtype), k_embed.to(org_dtype), sin, cos
class LlamaRoPE(nn.Module):
def __init__(self, config, device = None, dtype = None):
super().__init__()
if config.rope_scaling is not None:
self.rope_type = config.rope_scaling.get("rope_type", config.rope_type)
else:
self.rope_type = "default"
self.config = config
self.rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
inv_freq, self.attention_scaling = self.rope_init_fn(self.config, device)
self.register_buffer("inv_freq", inv_freq, persistent=False)
self.original_inv_freq = self.inv_freq
def _dynamic_frequency_update(self, position_ids, device):
seq_len = torch.max(position_ids) + 1
if seq_len > self.max_seq_len_cached:
inv_freq, self.attention_scaling = self.rope_init_fn(
self.config, device, seq_len=seq_len, **self.rope_kwargs
)
self.register_buffer("inv_freq", inv_freq, persistent=False)
self.max_seq_len_cached = seq_len
if seq_len < self.original_max_seq_len and self.max_seq_len_cached > self.original_max_seq_len:
self.register_buffer("inv_freq", self.original_inv_freq, persistent=False)
self.max_seq_len_cached = self.original_max_seq_len
@torch.no_grad()
def forward(self, x, position_ids):
if "dynamic" in self.rope_type:
self._dynamic_frequency_update(position_ids, device=x.device)
inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1)
position_ids_expanded = position_ids[:, None, :].float()
device_type = x.device.type
device_type = device_type if isinstance(device_type, str) and device_type != "mps" else "cpu"
with torch.autocast(device_type=device_type, enabled=False):
freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
emb = torch.cat((freqs, freqs), dim=-1)
cos = emb.cos()
sin = emb.sin()
cos = cos * self.attention_scaling
sin = sin * self.attention_scaling
return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
class Attention(nn.Module):
def __init__(self, config: Llama2Config, device=None, dtype=None, ops: Any = None):
def __init__(self, config: Llama2Config, layer_idx: int = None, device=None, dtype=None, ops: Any = None):
super().__init__()
self.num_heads = config.num_attention_heads
self.num_kv_heads = config.num_key_value_heads
self.hidden_size = config.hidden_size
self.layer_idx = layer_idx
self.head_dim = config.head_dim
self.inner_size = self.num_heads * self.head_dim
@ -286,6 +350,8 @@ class Attention(nn.Module):
self,
hidden_states: torch.Tensor,
attention_mask: Optional[torch.Tensor] = None,
past_key_value: Optional[Cache] = None,
cache_position: Optional[torch.LongTensor] = None,
freqs_cis: Optional[torch.Tensor] = None,
optimized_attention=None,
):
@ -303,13 +369,22 @@ class Attention(nn.Module):
if self.k_norm is not None:
xk = self.k_norm(xk)
xq, xk = apply_rope(xq, xk, freqs_cis=freqs_cis)
xq, xk, sin, cos = apply_rope(xq, xk, freqs_cis=freqs_cis)
if past_key_value is not None:
cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
xk, xv = past_key_value.update(xk, xv, self.layer_idx, cache_kwargs)
xk = xk.repeat_interleave(self.num_heads // self.num_kv_heads, dim=1)
xv = xv.repeat_interleave(self.num_heads // self.num_kv_heads, dim=1)
output = optimized_attention(xq, xk, xv, self.num_heads, mask=attention_mask, skip_reshape=True)
return self.o_proj(output)
out = self.o_proj(output)
if past_key_value is not None:
return out, past_key_value
return out
class MLP(nn.Module):
def __init__(self, config: Llama2Config, device=None, dtype=None, ops: Any = None):

View File

@ -1,6 +1,7 @@
from __future__ import annotations
import av
import re
import torchaudio
import torch
import comfy.model_management
@ -10,10 +11,258 @@ import io
import json
import random
import hashlib
import numpy as np
import node_helpers
import logging
from comfy.cli_args import args
from comfy.comfy_types import IO
from comfy.comfy_types import FileLocator
from dataclasses import asdict
from comfy.ldm.higgsv2.loudness import loudness
from comfy.ldm.higgsv2.preprocess import (
prepare_chatml_sample, Message, ChatMLSample, ChatMLDatasetSample, AudioContent, transcript_normalize
)
MULTISPEAKER_DEFAULT_SYSTEM_MESSAGE = """You are an AI assistant designed to convert text into speech.
If the user's message includes a [SPEAKER*] tag, do not read out the tag and generate speech for the following text, using the specified voice.
If no speaker tag is present, select a suitable voice on your own."""
class LoudnessNormalization:
CATEGORY = "audio"
RETURN_TYPES = ("AUDIO",)
FUNCTION = "normalize"
@classmethod
def INPUT_TYPES(s):
return {"required": {"audio": ("AUDIO", ),
"block_size": ("FLOAT", {"default": 0.400, "min": 0.1, "max": 1.0, "step": 0.05}),
"loudness_threshold": ("FLOAT", {"default": -23.0, "min": -70.0, "max": 0.0, "step": 0.5,
"tooltip": "Target loudness in LUFS. Common values are -23.0 (broadcast), -14.0 (streaming)."})}}
def normalize(self, audio, loudness_threshold, block_size):
sampling_rate = audio["sample_rate"]
waveform = audio["waveform"]
return {"waveform": loudness(waveform, sampling_rate, target_loudness = loudness_threshold, block_size = block_size), "sample_rate": sampling_rate}
def prepare_chatml_input(
clip,
input_tokens,
audio_contents,
sampling_rate,
postfix_str: str = "",
):
if hasattr(clip, "postfix"):
postfix_str = clip.postfix
if postfix_str:
postfix = clip.tokenizer.encode(postfix_str, add_special_tokens=False)
input_tokens.extend(postfix)
audio_ids_l = []
if audio_contents is not None:
if not hasattr(clip, "audio_tokenizer"):
raise ValueError("This model does not have an audio tokenizer. The chosen model may not support ChatML Format")
for audio_content in audio_contents:
audio_content.raw_audio = audio_content.raw_audio.squeeze(0)
if audio_content.raw_audio.shape[0] == 2:
audio_content.raw_audio = audio_content.raw_audio.mean(dim = 0, keepdim = True)
if audio_content.raw_audio.device != next(clip.audio_tokenizer.parameters()).device:
audio_content.raw_audio = audio_content.raw_audio.to(next(clip.audio_tokenizer.parameters()).device)
audio_ids = clip.audio_tokenizer.encode(audio_content.raw_audio, sampling_rate)
audio_ids_l.append(audio_ids.squeeze(0))
if len(audio_ids_l) > 0:
audio_ids_start = torch.tensor(
np.cumsum(np.array([0] + [audio_ids.shape[1] for audio_ids in audio_ids_l])),
dtype=torch.long,
device=audio_contents[0].raw_audio.device,
).to("cpu")[0:-1]
audio_ids_concat = torch.cat(audio_ids_l, dim=1).to("cpu")
else:
audio_ids_start = None
audio_ids_concat = None
sample = ChatMLDatasetSample(
input_ids=torch.LongTensor(input_tokens),
label_ids=None,
audio_ids_concat=audio_ids_concat,
audio_ids_start=audio_ids_start,
audio_waveforms_concat=None,
audio_waveforms_start=None,
audio_sample_rate=None,
audio_speaker_indices=None,
)
if hasattr(clip, "collator"):
sample.input_ids = sample.input_ids.cpu()
sample = clip.collator([sample])
inputs = asdict(sample)
for k, v in inputs.items():
if isinstance(v, torch.Tensor):
inputs[k] = v.to(clip.device)
return inputs
def postprocess_chatml(text: str) -> str:
speakers = set(re.findall(r'\[SPEAKER\d+\]', text))
skip_recon = True
if len(speakers) > 1:
parts = text.split('<|eot_id|>')
# keep the first <|eot_id|> and the last one
first_eot = parts[0] + '<|eot_id|>'
middle_parts = ''.join(parts[1:-1])
last_eot = '<|eot_id|>' + parts[-1]
text = first_eot + middle_parts + last_eot
skip_recon = False
return text, skip_recon
class CreateChatMLSample:
def __init__(self):
self.device = comfy.model_management.intermediate_device()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": (IO.STRING, {
"default": "SYSTEM: " + MULTISPEAKER_DEFAULT_SYSTEM_MESSAGE + "\n\n<|scene_desc_start|>\nSPEAKER0:masculine\nSPEAKER1:feminine\n<|scene_desc_end|>",
"multiline": True,
"dynamicPrompts": True,
"tooltip": (
"The conversations to be encoded. "
"To register a conversation start with SPEAKER-0: some text. "
"To add a system prompt start with system:"
),
}),
"clip": (IO.CLIP, {"tooltip": "The CLIP model used for tokenizing the text."}),
},
"optional": {
"audio": (IO.AUDIO, {
"tooltip": "An audio clip to be inserted into the conversation. To register add [audio]",
})
}
}
RETURN_TYPES = ("TOKENS",)
OUTPUT_TOOLTIPS = ("Turns text and audio into a ChatML Format.",)
FUNCTION = "convert_to_ml_format"
CATEGORY = "conditioning"
def convert_to_ml_format(self, clip, text, audio=None):
if audio is not None:
clip.load_model()
if hasattr(clip, "cond_stage_model"):
clip = clip.cond_stage_model
text = transcript_normalize(text)
messages = []
lines = text.splitlines()
sampling_rate = False
current_role = None
collecting_system = False
system_buffer = []
for line in lines:
line = line.strip()
if not line:
continue
# system start
if line.lower().startswith("system:"):
collecting_system = True
system_buffer.append(line[len("system:"):].strip())
continue
# while collecting system prompt
if collecting_system:
system_buffer.append(line)
if "<|scene_desc_end|>" in line or "SPEAKER-" in line:
system_prompt = "\n".join(system_buffer)# + "\n<|scene_desc_end|>"
messages.append(Message(role="system", content=system_prompt))
system_buffer = []
collecting_system = False
continue
# speaker lines SPEAKER-0: text
match = re.match(r"SPEAKER-(\d+):\s*(.*)", line, re.IGNORECASE)
if match:
speaker_id = match.group(1)
content = match.group(2)
current_role = f"[SPEAKER{speaker_id}] "
messages.append(Message(role = "user", content = current_role + content.strip()))
else:
# continuation line goes to last speaker or instruction
if current_role is not None and messages:
messages[-1].content += "\n" + line
# return normal input_ids
if not (len(messages) >= 1):
return (clip.tokenizer(text),)
all_text = "".join(msg.content for msg in messages if msg.role == "user")
# postprocess to allow multi-user speech
all_text, skip_recon = postprocess_chatml(all_text)
if not skip_recon:
lines = all_text.splitlines()
messages = [messages[0]] if messages[0].role == "system" else []
current_role = None
for line in lines:
line = line.strip()
if not line:
continue
match = re.match(r'(\[SPEAKER\d+\])\s*(.*)', line)
if match:
current_role = match.group(1)
content = match.group(2).strip() # only take the text after the tag
messages.append(Message(role="user", content=f"{current_role} {content}" if content else current_role))
else:
if current_role and messages:
messages[-1].content += "\n" + line
# dedepulicate the messages
for idx, m in enumerate(messages):
double_eot = "<|eot_id|><|eot_id|>"
if double_eot in m.content:
cut_index = m.content.index(double_eot)
messages[idx].content = m.content[:cut_index + (len(double_eot) // 2)]
break
if audio is not None:
# for audio cloning, the first message is a transcript, second is the audio,
# third is the request of what the model should say
waveform = audio["waveform"]
sampling_rate = audio["sample_rate"]
messages.insert(1, Message(
role = "assistant",
content = AudioContent(raw_audio = waveform, audio_url = "placeholder")
))
chat_ml_sample = ChatMLSample(messages)
input_tokens, audio_contents, _ = prepare_chatml_sample(
chat_ml_sample,
clip.tokenizer,
)
if audio is None:
audio_contents = None
out = prepare_chatml_input(clip, input_tokens, audio_contents, sampling_rate = sampling_rate)
return (out,)
class EmptyLatentAudio:
def __init__(self):
@ -586,6 +835,8 @@ NODE_CLASS_MAPPINGS = {
"LoadAudio": LoadAudio,
"PreviewAudio": PreviewAudio,
"ConditioningStableAudio": ConditioningStableAudio,
"LoudnessNormalization": LoudnessNormalization,
"CreateChatMLSample": CreateChatMLSample,
"RecordAudio": RecordAudio,
"TrimAudioDuration": TrimAudioDuration,
"SplitAudioChannels": SplitAudioChannels,
@ -604,6 +855,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"SaveAudio": "Save Audio (FLAC)",
"SaveAudioMP3": "Save Audio (MP3)",
"SaveAudioOpus": "Save Audio (Opus)",
"LoudnessNormalization": "Loudness Normalization",
"CreateChatMLSample": "Create ChatML Sample",
"RecordAudio": "Record Audio",
"TrimAudioDuration": "Trim Audio Duration",
"SplitAudioChannels": "Split Audio Channels",

View File

@ -0,0 +1,60 @@
from comfy.autoregressive_sampling import auto_sample
from comfy.comfy_types import IO
class AutoRegressiveGeneration:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL", {"tooltip": "The model used for generation."}),
"input_ids": ("TOKENS", ),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True, "tooltip": "The random seed used for controling the generation."}),
"max_new_length": ("INT", {"default": 1024, "min": 1, "max": 10_000, "tooltip": "The max length for generation."}),
"min_new_length": ("INT", {"default": 1, "min": 1, "max": 10_000, "tooltip": "The min length for generation."}),
"top_k": ("INT", {"default": 50, "min": 1, "max": 30_000, "tooltip": "Takes the top k of the most probable tokens."}),
"top_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Percentage of tokens to leave after generation (top most probable tokens)."}),
"temperature": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 50, "step": 0.01, "tooltip": "Temperature controls randomess by decreasing or increasing the probability of lesser likely tokens. Higher Temperature -> More Randomness"}),
"do_sample": ("BOOLEAN", {"default": False, "tooltip": "Add randomness in decoding the tokens."}),
}
}
RETURN_TYPES = ("GENERATED_TOKENS",)
FUNCTION = "generate"
CATEGORY = "sampling"
# for cuda graphs
_cached_autoregressive_sampler = None
def generate(self, model, input_ids, seed, max_new_length, min_new_length, top_k, top_p, temperature, do_sample):
return (auto_sample(self, model, input_ids, max_new_length, min_new_length, top_k, top_p, temperature, do_sample, seed = seed),)
class DecodeTokens:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"clip": (IO.CLIP, {"tooltip": "The model used for generation."}),
"generated_tokens": ("GENERATED_TOKENS", ),}
}
FUNCTION = "decode"
CATEGORY = "conditioning"
RETURN_TYPES = ("TEXT", "AUDIO")
def decode(self, clip, tokens):
clip.load_model()
if hasattr(clip.cond_stage_model, "decode_tokens"): # for special tokenizers
return clip.cond_stage_model.decode_tokens(tokens)
else:
return (clip.tokenizer.decode(tokens, skip_special_tokens=True), None)
NODE_CLASS_MAPPINGS = {
"DecodeTokens": DecodeTokens,
"AutoRegressiveGeneration": AutoRegressiveGeneration,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AutoRegressiveGeneration": "Autoregressive Generation",
"DecodeTokens": "Decode Tokens",
}

View File

@ -2354,6 +2354,7 @@ async def init_builtin_extra_nodes():
"nodes_model_patch.py",
"nodes_easycache.py",
"nodes_audio_encoder.py",
"nodes_autoregressive.py",
"nodes_rope.py",
"nodes_logic.py",
"nodes_nop.py",