feat(assets): SA3 音效补齐 radio 11+1 与 dungeon 13源+15pan(token 走 hf-mirror 实测可用)

- 模型 17/17 文件经镜像全量缓存(gated+token,直连本机不通;sfx.md/setup.md/asset-pipeline.md 镜像结论同步更新)
- 修复迁移遗留:stable_audio_3/models/ 源码子包被 .gitignore models/ 规则误伤丢失,从上游恢复并加白名单
- radio:arg_muffled/brake_screech 等叙事件落地(接线原有 L111/L154 零改动)+ phone_ring SYN→SA3 升级;dungeon 13 源 A/B 对齐(RMS 偏差 ≤0.3dB)全量同名替换,SYN 原版备份 tools/asset_fix_backup/sa3_ab_20260902/
- 关键玩法语义达标:echo 距离档 0.83<1.43<2.18<2.98s 严格递增;one-shot 真峰值全 ≤-3dBFS(解码回读法)
- check_all_assets 8 游戏全绿(radio 254/254、dungeon 303/303);playtest radio 221/dungeon 1610 全绿
This commit is contained in:
xsl
2026-09-02 19:35:38 +08:00
parent d870a75a26
commit 24c526af68
181 changed files with 5324 additions and 12 deletions
+5
View File
@@ -174,3 +174,8 @@ optimized/tflite/models/tflite/lora_cache/
# Rendered audio from the CLI / gradio backends. *.wav above already covers most of it,
# but the --file-format flag also writes .flac and .mp3.
optimized/*/output/
# 误伤修正(2026-09-02):上面的 models/ 规则会忽略源码子包 stable_audio_3/models/
# (factory.py/loading_utils.py 的依赖,迁移机器时曾因未入库而丢失,从上游 GitHub 重取)。
!stable_audio_3/models/
!stable_audio_3/models/**
@@ -0,0 +1,639 @@
import torch
from torch import nn
from torch.nn.utils import weight_norm
from torchaudio import transforms as T
from einops import rearrange
from ..inference.audio_utils import prepare_audio
from .transformer import TransformerBlock
def WNConv1d(*args, **kwargs):
return weight_norm(nn.Conv1d(*args, **kwargs))
def checkpoint(function, *args, **kwargs):
kwargs.setdefault("use_reentrant", False)
return torch.utils.checkpoint.checkpoint(function, *args, **kwargs)
class Transpose(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x, **kwargs):
return rearrange(x, '... a b -> ... b a')
def _zero_pad_modulo_sequence(x, size, dim=-2):
input_len = x.shape[dim]
pad_len = (size - input_len % size) % size
if pad_len > 0:
pad_shape = list(x.shape)
pad_shape[dim] = pad_len
x = torch.cat([x, torch.zeros(pad_shape, device=x.device, dtype=x.dtype)], dim=dim)
return x
class TransformerResamplingBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride, sliding_window = None, chunk_size = 128, chunk_midpoint_shift = False, type = 'encoder', transformer_depth = 3, checkpointing = False,
conformer = False, layer_scale = False, dim_heads = 128, differential = True, variable_stride = False, feat_scale = False,
sinusoidal_blocks = 0, mask_noise = 0, ff_mult = 3, mapping_bias = True, cross_attn = False, dyt = True, conv_mapping = False, freeze_backbone = False, **kwargs):
super().__init__()
if type not in ['encoder', 'decoder']:
raise ValueError(f"Unknown type {type}. Must be 'encoder' or 'decoder'")
self.checkpointing = checkpointing
transformer_dim = out_channels if type == 'encoder' else in_channels
transformers = []
self.in_channels = in_channels
self.out_channels = out_channels
self.variable_stride = variable_stride
self.stride = stride
self.mapping = WNConv1d(in_channels, out_channels, 3 if conv_mapping else 1, padding = 'same', bias = mapping_bias) if in_channels != out_channels else nn.Identity()
self.chunk_size = chunk_size
self.chunk_midpoint_shift = chunk_midpoint_shift
self.type = type
self.mask_noise = mask_noise
self.sliding_window_latents = sliding_window
self.sliding_window_seq = self._get_sliding_window_size(sliding_window, stride)
self.input_seg_size, self.output_seg_size, self.sub_chunk_size = self._get_seg_sizes(stride)
self.transformer_depth = transformer_depth
for i in range(transformer_depth):
sinusoidal = True if ((transformer_depth - i) < sinusoidal_blocks) else False
transformers.append(TransformerBlock(transformer_dim,
dim_heads = dim_heads,
causal = False,
zero_init_branch_outputs = True if not layer_scale else False,
norm_type = 'dyt' if dyt else 'rms_norm',
conformer = conformer,
layer_scale = layer_scale,
add_rope = True,
attn_kwargs={'qk_norm': "dyt" if dyt else "rms", "qk_norm_eps": 1e-3, "differential": differential, "feat_scale": feat_scale},
ff_kwargs={'mult': ff_mult, 'no_bias': False, "sinusoidal": sinusoidal},
norm_kwargs = {'eps': 1e-3},
cross_attend = cross_attn))
self.new_tokens = nn.Parameter(1e-5 * torch.randn(1, self.output_seg_size if not self.variable_stride else 1, out_channels if type == 'encoder' else in_channels))
self.transformers = nn.ModuleList(transformers)
if freeze_backbone:
for param in self.transformers.parameters():
param.requires_grad = False
self.new_tokens.requires_grad = False
def _get_sliding_window_size(self, window, stride, prepend_cond_length = 0):
if window is None:
return None
else:
return [(win * (stride + 1 + prepend_cond_length)) for win in window]
def _get_seg_sizes(self, stride, prepend_cond_length = 0):
sub_chunk_size = stride + 1 + prepend_cond_length
if self.sliding_window_latents is None:
assert (self.chunk_size % stride) == 0, f"Stride must fit evenly into chunk size:{self.chunk_size}"
input_seg_size = stride if self.type == 'encoder' else 1
output_seg_size = 1 if self.type == 'encoder' else stride
return input_seg_size, output_seg_size, sub_chunk_size
#@torch.compile
def forward(self, x, stride = None, return_features = False, override_new_tokens = None, prepend_cond = None, cross_attn_cond = None):
batch_size = x.shape[0]
input_length = x.shape[-1]
if return_features:
features = []
if stride == None:
input_seg_size = self.input_seg_size
output_seg_size = self.output_seg_size
sub_chunk_size = self.sub_chunk_size
sliding_window = self.sliding_window_seq
else:
if not self.variable_stride:
print("cannot override stride if variable_stride is not set")
prepend_cond_length = prepend_cond.shape[-2] if prepend_cond is not None else 0
input_seg_size, output_seg_size, sub_chunk_size = self._get_seg_sizes(stride, prepend_cond_length)
sliding_window = self._get_sliding_window_size(self.sliding_window_latents, stride, prepend_cond_length)
if self.type == 'encoder':
# Pad before mapping so silence zeros get projected through the mapping,
# rather than inserting raw zeros in the mapped space
if self.transformer_depth > 0:
if sliding_window is None:
pad_modulo = self.chunk_size
else:
pad_modulo = input_seg_size
x = _zero_pad_modulo_sequence(x, pad_modulo, dim=-1)
x = self.mapping(x)
if self.transformer_depth > 0:
x = rearrange(x, '... a b -> ... b a')
if return_features:
features.append(x)
if self.type != 'encoder':
if sliding_window is None:
active_stride = stride if stride is not None else self.stride
pad_modulo = self.chunk_size // active_stride
x = _zero_pad_modulo_sequence(x, pad_modulo)
else:
x = _zero_pad_modulo_sequence(x, input_seg_size)
x = rearrange(x, 'b (n c) d -> (b n) c d', c = input_seg_size)
new_token_seq_dim = -1 if not self.variable_stride else output_seg_size
new_tokens = self.new_tokens.expand([x.shape[0],new_token_seq_dim,-1])
if override_new_tokens is not None:
#print(f"Using override new tokens with shape {override_new_tokens.shape}, new tokens shape {new_tokens.shape}, x shape {x.shape}")
override_new_tokens = rearrange(override_new_tokens, 'b (n c) d -> (b n) c d', c = output_seg_size)
new_tokens = new_tokens + override_new_tokens
elif self.mask_noise > 0:
new_tokens = new_tokens + torch.randn_like(new_tokens) * self.mask_noise
x = torch.cat([x,new_tokens], dim = -2)
if prepend_cond is not None:
n = x.shape[0] // batch_size
cond_folded = prepend_cond.unsqueeze(1).expand(batch_size, n, prepend_cond.shape[-2], x.shape[-1]).reshape(n * batch_size, prepend_cond.shape[-2], x.shape[-1])
x = torch.cat([cond_folded, x], dim=-2)
x = rearrange(x, '(b n) c d -> b (n c) d', b=batch_size)#.contiguous()
# Fold into contiguous chunks if no sliding window
if sliding_window is None:
prepend_cond_length = prepend_cond.shape[-2] if prepend_cond is not None else 0
effective_chunk_size = self.chunk_size + self.chunk_size * (1 + prepend_cond_length) // (stride if stride is not None else self.stride)
if sliding_window is None and self.chunk_midpoint_shift:
split = self.transformer_depth // 2
shift = effective_chunk_size // 2
# First half: standard chunks
nc = x.shape[1] // effective_chunk_size
x = rearrange(x, 'b (nc cc) d -> (b nc) cc d', cc=effective_chunk_size)
cross_attn_first = None
if cross_attn_cond is not None:
cross_attn_first = cross_attn_cond.repeat_interleave(nc, dim=0)
for layer in self.transformers[:split]:
if self.checkpointing:
x = checkpoint(layer, x, context = cross_attn_first, self_attention_flash_sliding_window = None)
else:
x = layer(x, context = cross_attn_first)
if return_features:
features.append(rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size))
x = rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size)
# Second half: shifted chunks with sub-chunk repeat padding
# shift is always a multiple of sub_chunk_size, so slicing
# by shift gives whole sub-chunks in their original order
x = torch.cat([x[:, :shift, :], x, x[:, -shift:, :]], dim=1)
nc_shifted = x.shape[1] // effective_chunk_size
x = rearrange(x, 'b (nc cc) d -> (b nc) cc d', cc=effective_chunk_size)
cross_attn_second = None
if cross_attn_cond is not None:
cross_attn_second = cross_attn_cond.repeat_interleave(nc_shifted, dim=0)
for layer in self.transformers[split:]:
if self.checkpointing:
x = checkpoint(layer, x, context = cross_attn_second, self_attention_flash_sliding_window = None)
else:
x = layer(x, context = cross_attn_second)
if return_features:
feat = rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size)
features.append(feat[:, shift:-shift, :])
x = rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size)
x = x[:, shift:-shift, :]
else:
if sliding_window is None:
x = rearrange(x, 'b (nc cc) d -> (b nc) cc d', cc=effective_chunk_size)
for layer in self.transformers:
if self.checkpointing:
x = checkpoint(layer, x, context = cross_attn_cond, self_attention_flash_sliding_window = sliding_window)
else:
x = layer(x, context = cross_attn_cond, self_attention_flash_sliding_window = sliding_window)
if return_features:
features.append(x)
# Unfold chunks back to original batch
if sliding_window is None:
x = rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size)
x = rearrange(x, 'b (n c) d -> (b n) c d', c=sub_chunk_size)
x = x[:,-output_seg_size:,:]
x = rearrange(x, '(b n) c d -> b d (n c)', b=batch_size)
if self.type == 'decoder':
x = self.mapping(x)
if return_features:
return x, features
else:
return x
class SAMEEncoder(nn.Module):
def __init__(self,
in_channels=2,
channels=128,
latent_dim=32,
c_mults = [1, 2, 4, 8],
strides = [2, 4, 8, 8],
transformer_depths = [3,3,3,3],
sliding_window = None,
checkpointing = False,
conformer = False,
layer_scale = False,
causal = False,
differential = True,
variable_stride = False,
mask_noise = 0.0,
conv_mapping = False,
freeze_backbone = False,
**kwargs
):
super().__init__()
self.in_channels = in_channels
self.strides = strides
channel_dims = [c * channels for c in c_mults]
channel_dims = [in_channels] + channel_dims
self.depth = len(c_mults)
layers = []
for i in range(self.depth):
layers += [TransformerResamplingBlock(in_channels=channel_dims[i], out_channels=channel_dims[i+1], stride=strides[i], transformer_depth = transformer_depths[i],
sliding_window = sliding_window, checkpointing = checkpointing, conformer = conformer, layer_scale = layer_scale, causal = causal,
differential = differential, variable_stride = variable_stride, mask_noise = mask_noise, conv_mapping = conv_mapping,
freeze_backbone = freeze_backbone, **kwargs)]
layers += [Transpose(), nn.Linear(channel_dims[-1], latent_dim), Transpose()]
self.layers = nn.ModuleList(layers)
if freeze_backbone:
for param in self.layers[-2].parameters():
param.requires_grad = False
def forward(self, x, override_stride = None, return_features = False, **kwargs):
if override_stride != None:
assert isinstance(override_stride, list), "override_stride must be a list"
assert len(override_stride) == self.depth, "override_stride must be a list containing strides for every layer"
for i, layer in enumerate(self.layers):
if isinstance(layer, TransformerResamplingBlock):
if override_stride != None:
stride = override_stride[i]
else:
stride = None
if return_features:
x, features = layer(x, stride = stride, return_features = True)
else:
x = layer(x, stride = stride)
else:
x = layer(x)
if return_features:
return x, features
else:
return x
class SAMEDecoder(nn.Module):
def __init__(self,
out_channels=2,
channels=128,
latent_dim=32,
c_mults = [1, 2, 4, 8],
strides = [2, 4, 8, 8],
transformer_depths = [3,3,3,3],
sliding_window = None,
checkpointing = False,
conformer = False,
layer_scale = False,
causal = False,
differential = True,
variable_stride = False,
sinusoidal_blocks = [0,0,0,0],
mask_noise = 0.0,
conv_mapping = False,
freeze_backbone = False,
**kwargs
):
super().__init__()
channel_dims = [c * channels for c in c_mults]
channel_dims = [out_channels] + channel_dims
self.depth = len(c_mults)
layers = [Transpose(), nn.Linear(latent_dim, channel_dims[-1]), Transpose()]
for i in range(self.depth, 0, -1):
layers += [TransformerResamplingBlock(in_channels=channel_dims[i], out_channels=channel_dims[i-1], stride=strides[i-1], type = 'decoder', transformer_depth = transformer_depths[i-1],
sliding_window = sliding_window, checkpointing = checkpointing, conformer = conformer, layer_scale = layer_scale, causal = causal, differential = differential,
variable_stride = variable_stride, sinusoidal_blocks = sinusoidal_blocks[i-1], mask_noise = mask_noise, conv_mapping = conv_mapping,
freeze_backbone = freeze_backbone, **kwargs)]
self.layers = nn.ModuleList(layers)
if freeze_backbone:
for param in self.layers[1].parameters():
param.requires_grad = False
def forward(self, x, override_stride = None, **kwargs):
if override_stride != None:
assert isinstance(override_stride, list), "override_stride must be a list"
assert len(override_stride) == self.depth, "override_stride must be a list containing strides for every layer"
transformer_layer_index = 0
for i, layer in enumerate(self.layers):
if isinstance(layer, TransformerResamplingBlock):
if override_stride != None:
stride = override_stride[transformer_layer_index]
else:
stride = None
x = layer(x, stride = stride)
transformer_layer_index += 1
else:
x = layer(x)
return x
class AudioAutoencoder(nn.Module):
def __init__(
self,
encoder,
decoder,
latent_dim,
downsampling_ratio,
sample_rate,
io_channels=2,
bottleneck: nn.Module = None,
pretransform: nn.Module = None,
in_channels = None,
out_channels = None,
soft_clip = False,
freeze_pretransform = False
):
super().__init__()
self.downsampling_ratio = downsampling_ratio
self.sample_rate = sample_rate
self.latent_dim = latent_dim
self.io_channels = io_channels
self.in_channels = io_channels
self.out_channels = io_channels
self.min_length = self.downsampling_ratio
if in_channels is not None:
self.in_channels = in_channels
if out_channels is not None:
self.out_channels = out_channels
self.bottleneck = bottleneck
self.encoder = encoder
self.decoder = decoder
self.pretransform = pretransform
self.freeze_pretransform = freeze_pretransform
if self.pretransform is not None:
if self.freeze_pretransform:
for p in self.pretransform.parameters():
p.requires_grad = False
else:
for p in self.pretransform.parameters():
p.requires_grad = True
self.soft_clip = soft_clip
self.is_discrete = False
def encode(self, audio, return_info=False, skip_pretransform=False, iterate_batch=False, return_pretransform = False, **kwargs):
info = {}
if self.pretransform is not None and not skip_pretransform:
if self.pretransform.enable_grad:
if iterate_batch:
audios = []
for i in range(audio.shape[0]):
audios.append(self.pretransform.encode(audio[i:i+1]))
audio = torch.cat(audios, dim=0)
else:
audio = self.pretransform.encode(audio)
else:
with torch.no_grad():
if iterate_batch:
audios = []
for i in range(audio.shape[0]):
audios.append(self.pretransform.encode(audio[i:i+1]))
audio = torch.cat(audios, dim=0)
else:
audio = self.pretransform.encode(audio)
if self.encoder is not None:
if iterate_batch:
latents = []
for i in range(audio.shape[0]):
latents.append(self.encoder(audio[i:i+1], **kwargs))
latents = torch.cat(latents, dim=0)
else:
latents = self.encoder(audio, **kwargs)
else:
latents = audio
if self.bottleneck is not None:
# TODO: Add iterate batch logic, needs to merge the info dicts
latents, bottleneck_info = self.bottleneck.encode(latents, return_info=True, **kwargs)
info.update(bottleneck_info)
if return_info and return_pretransform:
return latents, info, audio
elif return_info:
return latents, info
elif return_pretransform:
return latents, audio
else:
return latents
def decode(self, latents, iterate_batch=False, return_loss = False, **kwargs):
if self.bottleneck is not None:
if iterate_batch:
decoded = []
for i in range(latents.shape[0]):
decoded.append(self.bottleneck.decode(latents[i:i+1]))
latents = torch.cat(decoded, dim=0)
else:
latents = self.bottleneck.decode(latents)
if iterate_batch:
decoded = []
for i in range(latents.shape[0]):
decoded.append(self.decoder(latents[i:i+1], **kwargs))
decoded = torch.cat(decoded, dim=0)
else:
if return_loss:
decoded, loss = self.decoder(latents, **kwargs)
else:
decoded = self.decoder(latents, **kwargs)
if self.pretransform is not None:
if self.pretransform.enable_grad:
if iterate_batch:
decodeds = []
for i in range(decoded.shape[0]):
decodeds.append(self.pretransform.decode(decoded[i:i+1]))
decoded = torch.cat(decodeds, dim=0)
else:
decoded = self.pretransform.decode(decoded)
else:
with torch.no_grad():
if iterate_batch:
decodeds = []
for i in range(latents.shape[0]):
decodeds.append(self.pretransform.decode(decoded[i:i+1]))
decoded = torch.cat(decodeds, dim=0)
else:
decoded = self.pretransform.decode(decoded)
if self.soft_clip:
decoded = torch.tanh(decoded)
if return_loss:
return decoded, loss
else:
return decoded
def preprocess_audio_for_encoder(self, audio, in_sr):
'''
Preprocess single audio tensor (Channels x Length) to be compatible with the encoder.
If the model is mono, stereo audio will be converted to mono.
Audio will be silence-padded to be a multiple of the model's downsampling ratio.
Audio will be resampled to the model's sample rate.
The output will have batch size 1 and be shape (1 x Channels x Length)
'''
return self.preprocess_audio_list_for_encoder([audio], [in_sr])
def preprocess_audio_list_for_encoder(self, audio_list, in_sr_list):
'''
Preprocess a [list] of audio (Channels x Length) into a batch tensor to be compatable with the encoder.
The audio in that list can be of different lengths and channels.
in_sr can be an integer or list. If it's an integer it will be assumed it is the input sample_rate for every audio.
All audio will be resampled to the model's sample rate.
Audio will be silence-padded to the longest length, and further padded to be a multiple of the model's downsampling ratio.
If the model is mono, all audio will be converted to mono.
The output will be a tensor of shape (Batch x Channels x Length)
'''
batch_size = len(audio_list)
if isinstance(in_sr_list, int):
in_sr_list = [in_sr_list]*batch_size
assert len(in_sr_list) == batch_size, "list of sample rates must be the same length of audio_list"
new_audio = []
max_length = 0
# resample & find the max length
for i in range(batch_size):
audio = audio_list[i]
in_sr = in_sr_list[i]
if len(audio.shape) == 3 and audio.shape[0] == 1:
# batchsize 1 was given by accident. Just squeeze it.
audio = audio.squeeze(0)
elif len(audio.shape) == 1:
# Mono signal, channel dimension is missing, unsqueeze it in
audio = audio.unsqueeze(0)
assert len(audio.shape)==2, "Audio should be shape (Channels x Length) with no batch dimension"
# Resample audio
if in_sr != self.sample_rate:
resample_tf = T.Resample(in_sr, self.sample_rate).to(audio.device)
audio = resample_tf(audio)
new_audio.append(audio)
if audio.shape[-1] > max_length:
max_length = audio.shape[-1]
# Pad every audio to the same length, multiple of model's downsampling ratio
padded_audio_length = max_length + (self.min_length - (max_length % self.min_length)) % self.min_length
for i in range(batch_size):
# Pad it & if necessary, mixdown/duplicate stereo/mono channels to support model
new_audio[i] = prepare_audio(new_audio[i], in_sr=in_sr, target_sr=in_sr, target_length=padded_audio_length,
target_channels=self.in_channels, device=new_audio[i].device).squeeze(0)
# convert to tensor
return torch.stack(new_audio)
def encode_audio(self, audio, chunked=False, overlap=32, chunk_size=128, **kwargs):
'''
Encode audios into latents. Audios should already be preprocesed by preprocess_audio_for_encoder.
If chunked is True, split the audio into chunks of a given maximum size chunk_size, with given overlap.
Overlap and chunk_size params are both measured in number of latents (not audio samples)
# and therefore you likely could use the same values with decode_audio.
A overlap of zero will cause discontinuity artefacts. Overlap should be => receptive field size.
Every autoencoder will have a different receptive field size, and thus ideal overlap.
You can determine it empirically by diffing unchunked vs chunked output and looking at maximum diff.
The final chunk may have a longer overlap in order to keep chunk_size consistent for all chunks.
Smaller chunk_size uses less memory, but more compute.
The chunk_size vs memory tradeoff isn't linear, and possibly depends on the GPU and CUDA version
For example, on a A6000 chunk_size 128 is overall faster than 256 and 512 even though it has more chunks
'''
samples_per_latent = int(self.downsampling_ratio)
if not chunked or audio.shape[-1] < chunk_size * samples_per_latent:
return self.encode(audio, **kwargs)
# chunk_size/overlap are in latent units; scale to samples for slicing.
chunk_size_samples = chunk_size * samples_per_latent
hop_samples = (chunk_size - overlap) * samples_per_latent
total_samples = audio.shape[-1]
# Anchor the final chunk to the signal end (may overlap more than `overlap`).
chunk_starts = list(range(0, total_samples - chunk_size_samples + 1, hop_samples))
if chunk_starts[-1] != total_samples - chunk_size_samples:
chunk_starts.append(total_samples - chunk_size_samples)
encoded_chunks = [self.encode(audio[..., s:s + chunk_size_samples]) for s in chunk_starts]
total_latents = total_samples // samples_per_latent
half_overlap_latents = overlap // 2
output = audio.new_zeros(*encoded_chunks[0].shape[:-1], total_latents)
num_chunks = len(chunk_starts)
# Trim half the overlap off inner edges (edges facing a neighbour chunk).
for i, (start_sample, chunk) in enumerate(zip(chunk_starts, encoded_chunks)):
is_first = i == 0
is_last = i == num_chunks - 1
out_start = (total_latents - chunk_size) if is_last else (start_sample // samples_per_latent)
left = 0 if is_first else half_overlap_latents
right = chunk_size if is_last else chunk_size - half_overlap_latents
output[..., out_start + left : out_start + right] = chunk[..., left:right]
return output
def decode_audio(self, latents, chunked=False, overlap=32, chunk_size=128, **kwargs):
'''
Decode latents to audio.
If chunked is True, split the latents into chunks of a given maximum size chunk_size, with given overlap, both of which are measured in number of latents.
A overlap of zero will cause discontinuity artefacts. Overlap should be => receptive field size.
Every autoencoder will have a different receptive field size, and thus ideal overlap.
You can determine it empirically by diffing unchunked vs chunked audio and looking at maximum diff.
The final chunk may have a longer overlap in order to keep chunk_size consistent for all chunks.
Smaller chunk_size uses less memory, but more compute.
The chunk_size vs memory tradeoff isn't linear, and possibly depends on the GPU and CUDA version
For example, on a A6000 chunk_size 128 is overall faster than 256 and 512 even though it has more chunks
'''
if not chunked or latents.shape[-1] < chunk_size:
return self.decode(latents, **kwargs)
# chunk_size/overlap are in latent units — same as `latents`, no scaling.
samples_per_latent = int(self.downsampling_ratio)
hop_latents = chunk_size - overlap
total_latents = latents.shape[-1]
# Anchor the final chunk to the signal end (may overlap more than `overlap`).
chunk_starts = list(range(0, total_latents - chunk_size + 1, hop_latents))
if chunk_starts[-1] != total_latents - chunk_size:
chunk_starts.append(total_latents - chunk_size)
decoded_chunks = [self.decode(latents[..., s:s + chunk_size]) for s in chunk_starts]
total_samples = total_latents * samples_per_latent
chunk_size_samples = chunk_size * samples_per_latent
half_overlap_samples = (overlap // 2) * samples_per_latent
output = latents.new_zeros(*decoded_chunks[0].shape[:-1], total_samples)
num_chunks = len(chunk_starts)
# Trim half the overlap off inner edges (edges facing a neighbour chunk).
for i, (start_latent, chunk) in enumerate(zip(chunk_starts, decoded_chunks)):
is_first = i == 0
is_last = i == num_chunks - 1
out_start = (total_samples - chunk_size_samples) if is_last else (start_latent * samples_per_latent)
left = 0 if is_first else half_overlap_samples
right = chunk_size_samples if is_last else chunk_size_samples - half_overlap_samples
output[..., out_start + left : out_start + right] = chunk[..., left:right]
return output
@@ -0,0 +1,84 @@
import math
import torch
from torch import nn
from torch.nn.utils import weight_norm
def get_activation(activation, channels=None) -> nn.Module:
if activation == "elu":
return nn.ELU()
elif activation == "none":
return nn.Identity()
else:
raise ValueError(f"Unknown activation {activation}")
def WNConv1d(*args, **kwargs):
return weight_norm(nn.Conv1d(*args, **kwargs))
class ResidualUnit(nn.Module):
def __init__(self, in_channels, out_channels, dilation, depthwise=False, bias=True):
super().__init__()
self.dilation = dilation
padding = (dilation * (7-1)) // 2
self.layers = nn.Sequential(
get_activation("elu"),
WNConv1d(in_channels=in_channels, out_channels=out_channels,
kernel_size=7, dilation=dilation, padding=padding, groups=1 if not depthwise else out_channels, bias=bias),
get_activation("elu"),
WNConv1d(in_channels=out_channels, out_channels=out_channels,
kernel_size=1, bias=bias)
)
def forward(self, x):
res = x
x = self.layers(x)
return x + res
class FourierFeatures(nn.Module):
def __init__(self, in_features, out_features, std=16.):
super().__init__()
assert out_features % 2 == 0
self.register_buffer('weight', torch.randn([out_features // 2, in_features]) * std)
def forward(self, input):
f = 2 * math.pi * input @ self.weight.T
return torch.cat([f.cos(), f.sin()], dim=-1)
class ExpoFourierFeatures(nn.Module):
def __init__(self, dim, min_freq=0.5, max_freq=10000.0):
super().__init__()
self.dim = dim
self.min_freq = min_freq
self.max_freq = max_freq
@torch.amp.autocast("cuda",enabled=False)
def forward(self, t):
"""
t: [B] tensor.
"""
in_dtype = t.dtype
t = t.float()
if t.dim() == 1:
t = t.unsqueeze(-1)
half_dim = self.dim // 2
# Calculate frequencies (safely in FP32)
ramp = torch.linspace(0, 1, half_dim, device=t.device, dtype=torch.float32)
log_min = math.log(self.min_freq)
log_max = math.log(self.max_freq)
freqs = torch.exp(ramp * (log_max - log_min) + log_min)
# Calculate arguments (safely in FP32)
args = t * freqs * 2 * math.pi
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
return embedding.to(in_dtype)
@@ -0,0 +1,71 @@
import torch
from torch import nn
class SoftNormBottleneck(nn.Module):
def __init__(self, dim = 32, noise_augment_dim=0, noise_regularize = False, auto_scale = False, freeze = False):
super().__init__()
self.noise_augment_dim = noise_augment_dim
self.scaling_factor = nn.Parameter(torch.ones(1,dim,1))
self.bias = nn.Parameter(torch.zeros(1,dim,1))
self.noise_scaling_factor = nn.Parameter(torch.ones(1,noise_augment_dim,1))
self.noise_regularize = noise_regularize
self.freeze = freeze
if self.freeze:
self.scaling_factor.requires_grad = False
self.bias.requires_grad = False
self.noise_scaling_factor.requires_grad = False
if auto_scale:
running_std = torch.ones(1)
self.register_parameter("running_std", nn.Parameter(running_std, requires_grad=False))
def encode(self, x, return_info=False, **kwargs):
info = {}
x = x * self.scaling_factor + self.bias
if self.training and hasattr(self,"running_std") and not self.freeze:
# Update running std
self.running_std.data = (self.running_std.data * 0.999 + x.std().detach() * 0.001).clamp(min = 1e-4)
if hasattr(self, "running_std"):
x = x / self.running_std
if self.training and return_info:
var = (x.std(dim=-1) ** 2).clip(min = 1e-4)
logvar = torch.log(var)
mean = x.mean(dim=-1)
loss = (mean * mean + var - logvar - 1).mean()
var = (x.std(dim=-2) ** 2).clip(min = 1e-4)
logvar = torch.log(var)
mean = x.mean(dim=-2)
loss = loss + 0.4 * (mean * mean + var - logvar - 1).mean()
info["softnorm_loss"] = loss
if return_info:
return x, info
return x
def decode(self, x, **kwargs):
if hasattr(self, "running_std"):
x = x * self.running_std
if self.noise_regularize:
if hasattr(self, "running_std"):
scaling = self.running_std
else:
scaling = x.std(dim = -1).unsqueeze(-1)
if self.training:
scale = 5e-2
else:
scale = 1e-3
noise = torch.randn_like(x) * scaling * scale
x = x + noise
if self.noise_augment_dim > 0:
noise = self.noise_scaling_factor * torch.randn(x.shape[0], self.noise_augment_dim,
x.shape[-1]).type_as(x)
x = torch.cat([x, noise], dim=1)
return x
@@ -0,0 +1,318 @@
#Heavily influenced by https://github.com/facebookresearch/audiocraft/blob/main/audiocraft/modules/conditioners.py
import torch
import logging, warnings
import typing as tp
from enum import Enum
from math import pi
from typing import List, Union
from torch import Tensor, nn
from einops import rearrange
from .blocks import ExpoFourierFeatures
from .utils import enable_torch_compile
import os
class PaddingMode(str, Enum):
"""Enum for handling padding in text conditioner embeddings."""
NONE = "none" # No padding handling (raw embeddings with pad token)
ZERO = "zero" # Zero out padding positions (default)
LEARNED = "learned" # Use learned padding embedding
class Conditioner(nn.Module):
def __init__(
self,
dim: int,
output_dim: int,
project_out: bool = False,
padding_mode: str = "zero"
):
super().__init__()
self.dim = dim
self.output_dim = output_dim
self.padding_mode = padding_mode
self.proj_out = nn.Linear(dim, output_dim) if (dim != output_dim or project_out) else nn.Identity()
# Learned padding embedding (only created if needed)
if padding_mode == "learned" or padding_mode == PaddingMode.LEARNED:
self.padding_embedding = nn.Parameter(torch.randn(output_dim) * 0.02)
def apply_padding(self, embeddings: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
"""
Apply padding handling based on padding_mode.
Args:
embeddings: [batch, seq_len, dim] - the embeddings to process
attention_mask: [batch, seq_len] bool/int, True/1 = valid token
Returns:
embeddings with padding handled according to mode
"""
mode = self.padding_mode
if isinstance(mode, str):
mode = PaddingMode(mode)
if mode == PaddingMode.NONE:
return embeddings
elif mode == PaddingMode.ZERO:
return embeddings * attention_mask.unsqueeze(-1).float()
elif mode == PaddingMode.LEARNED:
mask_expanded = attention_mask.unsqueeze(-1).bool()
return torch.where(
mask_expanded,
embeddings,
self.padding_embedding.unsqueeze(0).unsqueeze(0).expand_as(embeddings)
)
else:
raise ValueError(f"Unknown padding mode: {mode}")
def forward(self, x: tp.Any) -> tp.Any:
raise NotImplementedError()
class LearnedPositionalEmbedding(nn.Module):
def __init__(self, dim: int, std: float = 16.0):
super().__init__()
assert (dim % 2) == 0
half_dim = dim // 2
self.weights = nn.Parameter(torch.randn(half_dim) * std)
def forward(self, x: Tensor) -> Tensor:
x = rearrange(x, "b -> b 1")
freqs = x * rearrange(self.weights, "d -> 1 d") * 2 * pi
fouriered = torch.cat((freqs.sin(), freqs.cos()), dim=-1)
fouriered = torch.cat((x, fouriered), dim=-1)
return fouriered
def TimePositionalEmbedding(dim: int, out_features: int) -> nn.Module:
return nn.Sequential(
LearnedPositionalEmbedding(dim),
nn.Linear(in_features=dim + 1, out_features=out_features),
)
class NumberEmbedder(nn.Module):
def __init__(
self,
features: int,
dim: int = 256,
fourier_features_type: tp.Literal["learned", "expo"] = "learned"
):
super().__init__()
self.features = features
if fourier_features_type == "expo":
self.embedding = nn.Sequential(ExpoFourierFeatures(dim=dim), nn.Linear(in_features=dim, out_features=features))
else:
self.embedding = TimePositionalEmbedding(dim=dim, out_features=features)
def forward(self, x: Union[List[float], Tensor]) -> Tensor:
if not torch.is_tensor(x):
device = next(self.embedding.parameters()).device
x = torch.tensor(x, device=device)
assert isinstance(x, Tensor)
shape = x.shape
x = rearrange(x, "... -> (...)")
embedding = self.embedding(x)
x = embedding.view(*shape, self.features)
return x # type: ignore
class NumberConditioner(Conditioner):
'''
Conditioner that takes a list of floats, normalizes them for a given range, and returns a list of embeddings
'''
def __init__(self,
output_dim: int,
min_val: float=0,
max_val: float=1,
fourier_features_type : tp.Literal["learned", "expo"] = "learned"
):
super().__init__(output_dim, output_dim)
self.min_val = min_val
self.max_val = max_val
self.embedder = NumberEmbedder(features=output_dim, fourier_features_type=fourier_features_type)
def forward(self, floats: tp.List[float], device=None) -> tp.Any:
self.embedder.to(device)
# Cast the inputs to floats
floats = [float(x) for x in floats]
floats = torch.tensor(floats).to(device)
floats = floats.clamp(self.min_val, self.max_val)
normalized_floats = (floats - self.min_val) / (self.max_val - self.min_val)
# Cast floats to same type as embedder
embedder_dtype = next(self.embedder.parameters()).dtype
normalized_floats = normalized_floats.to(embedder_dtype)
float_embeds = self.embedder(normalized_floats).unsqueeze(1)
return [float_embeds, torch.ones(float_embeds.shape[0], 1).to(device)]
class T5GemmaConditioner(Conditioner):
T5GEMMA_MODELS = ["google/t5gemma-b-b-ul2"]
T5GEMMA_MODEL_DIMS = {
"google/t5gemma-b-b-ul2": 768,
}
def __init__(
self,
output_dim: int,
model_name: str = "google/t5gemma-b-b-ul2",
max_length: str = 128,
enable_grad: bool = False,
project_out: bool = False,
padding_mode: str = "zero",
model_path: str = None,
repo_id: str = None,
subfolder: str = None,
):
assert model_name in self.T5GEMMA_MODELS, f"Unknown T5 model name: {model_name}"
super().__init__(self.T5GEMMA_MODEL_DIMS[model_name], output_dim, project_out=project_out, padding_mode=padding_mode)
load_from = model_path or repo_id or model_name
self.max_length = max_length
self.enable_grad = enable_grad
# Set environment variables to disable progress bars BEFORE importing transformers
# This is the most reliable way to suppress HuggingFace progress bars
prev_hf_hub = os.environ.get("HF_HUB_DISABLE_PROGRESS_BARS")
prev_transformers = os.environ.get("TRANSFORMERS_VERBOSITY")
os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
os.environ["TRANSFORMERS_VERBOSITY"] = "error"
# Suppress logging from transformers
previous_level = logging.root.manager.disable
logging.disable(logging.ERROR)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
try:
from transformers import T5GemmaEncoderModel, AutoTokenizer, AutoConfig
logging.info(f"Loading T5Gemma tokenizer and model from: {load_from}")
hf_kwargs = {"subfolder": subfolder} if subfolder else {}
self.tokenizer = AutoTokenizer.from_pretrained(load_from, **hf_kwargs)
config = AutoConfig.from_pretrained(load_from, **hf_kwargs)
config.is_encoder_decoder = False
model = T5GemmaEncoderModel.from_pretrained(load_from, config=config, **hf_kwargs).train(enable_grad).requires_grad_(enable_grad)
finally:
logging.disable(previous_level)
# Restore environment variables
if prev_hf_hub is None:
os.environ.pop("HF_HUB_DISABLE_PROGRESS_BARS", None)
else:
os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = prev_hf_hub
if prev_transformers is None:
os.environ.pop("TRANSFORMERS_VERBOSITY", None)
else:
os.environ["TRANSFORMERS_VERBOSITY"] = prev_transformers
# Compile the model to reduce CPU-GPU kernel launch overhead,
# which is sensitive to CPU contention from DataLoader workers
if enable_torch_compile:
model = torch.compile(model)
if self.enable_grad:
self.model = model
else:
self.__dict__["model"] = model
self._device_initialized = False
def forward(self, inputs: tp.Union[tp.List[str], tp.List[tp.Dict[str, torch.Tensor]]], device: tp.Union[torch.device, str]) -> tp.Tuple[torch.Tensor, torch.Tensor]:
# Only move to device once (avoid overhead on every forward call)
if not self._device_initialized:
self.model.to(device)
self.proj_out.to(device)
self.model.eval()
self._device_initialized = True
# Handle pre-tokenized inputs (dicts with input_ids/attention_mask from DataLoader workers)
# or raw strings (from demo generation / inference)
if isinstance(inputs[0], dict):
input_ids = torch.stack([x["input_ids"] for x in inputs]).to(device, non_blocking=True)
attention_mask = torch.stack([x["attention_mask"] for x in inputs]).to(device, non_blocking=True).to(torch.bool)
else:
encoded = self.tokenizer(
inputs,
truncation=True,
max_length=self.max_length,
padding="max_length",
return_tensors="pt",
)
input_ids = encoded["input_ids"].to(device, non_blocking=True)
attention_mask = encoded["attention_mask"].to(device, non_blocking=True).to(torch.bool)
with torch.no_grad():
embeddings = self.model(
input_ids=input_ids, attention_mask=attention_mask
)["last_hidden_state"]
# Cast embeddings to same type as proj_out, unless proj_out is Identity
if not isinstance(self.proj_out, nn.Identity):
proj_out_dtype = next(self.proj_out.parameters()).dtype
embeddings = embeddings.to(proj_out_dtype)
embeddings = self.proj_out(embeddings)
embeddings = self.apply_padding(embeddings, attention_mask)
return embeddings, attention_mask
class MultiConditioner(nn.Module):
"""
A module that applies multiple conditioners to an input dictionary based on the keys
Args:
conditioners: a dictionary of conditioners with keys corresponding to the keys of the conditioning input dictionary (e.g. "prompt")
default_keys: a dictionary of default keys to use if the key is not in the input dictionary (e.g. {"prompt_t5": "prompt"})
"""
def __init__(self, conditioners: tp.Dict[str, Conditioner], default_keys: tp.Dict[str, str] = {}, pre_encoded_keys: tp.List[str] = []):
super().__init__()
self.conditioners = nn.ModuleDict(conditioners)
self.default_keys = default_keys
self.pre_encoded_keys = pre_encoded_keys
def forward(self, batch_metadata: tp.List[tp.Dict[str, tp.Any]], device: tp.Union[torch.device, str]) -> tp.Dict[str, tp.Any]:
output = {}
for key, conditioner in self.conditioners.items():
condition_key = key
conditioner_inputs = []
for x in batch_metadata:
if condition_key not in x:
if condition_key in self.default_keys:
condition_key = self.default_keys[condition_key]
else:
raise ValueError(f"Conditioner key {condition_key} not found in batch metadata")
#Unwrap the condition info if it's a single-element list or tuple, this is to support collation functions that wrap everything in a list
if isinstance(x[condition_key], list) or isinstance(x[condition_key], tuple) and len(x[condition_key]) == 1:
conditioner_input = x[condition_key][0]
else:
conditioner_input = x[condition_key]
conditioner_inputs.append(conditioner_input)
if key in self.pre_encoded_keys:
output[key] = [torch.stack(conditioner_inputs, dim=0).to(device), None]
else:
output[key] = conditioner(conditioner_inputs, device)
return output
@@ -0,0 +1,253 @@
import torch
from torch import nn
import typing as tp
from .conditioners import MultiConditioner
from .dit import DiffusionTransformer
from stable_audio_3.inference.distribution_shift import FluxDistributionShift, DistributionShift, LogSNRShift, IdentityDistributionShift
from time import time
class Profiler:
def __init__(self):
self.ticks = [[time(), None]]
def tick(self, msg):
self.ticks.append([time(), msg])
def __repr__(self):
rep = 80 * "=" + "\n"
for i in range(1, len(self.ticks)):
msg = self.ticks[i][1]
ellapsed = self.ticks[i][0] - self.ticks[i - 1][0]
rep += msg + f": {ellapsed*1000:.2f}ms\n"
rep += 80 * "=" + "\n\n\n"
return rep
class ConditionedDiffusionModelWrapper(nn.Module):
"""
A diffusion model that takes in conditioning
"""
def __init__(
self,
model: nn.Module,
conditioner: MultiConditioner,
io_channels,
sample_rate,
min_input_length: int,
diffusion_objective: tp.Literal["v", "rectified_flow", "rf_denoiser"] = "v",
distribution_shift_options = None,
sampling_distribution_shift_options = None,
mask_padding_attention: bool = False,
use_effective_length_for_schedule: bool = False,
pretransform: tp.Optional[nn.Module] = None,
cross_attn_cond_ids: tp.List[str] = [],
global_cond_ids: tp.List[str] = [],
input_concat_ids: tp.List[str] = [],
local_add_cond_ids: tp.List[str] = [],
modular_local_cond_ids: tp.List[str] = [],
prepend_cond_ids: tp.List[str] = [],
):
super().__init__()
self.model = model
self.conditioner = conditioner
self.io_channels = io_channels
self.sample_rate = sample_rate
self.diffusion_objective = diffusion_objective
self.pretransform = pretransform
self.cross_attn_cond_ids = cross_attn_cond_ids
self.global_cond_ids = global_cond_ids
self.input_concat_ids = input_concat_ids
self.local_add_cond_ids = local_add_cond_ids
self.modular_local_cond_ids = modular_local_cond_ids
self.prepend_cond_ids = prepend_cond_ids
self.min_input_length = min_input_length
self.mask_padding_attention = mask_padding_attention
self.use_effective_length_for_schedule = use_effective_length_for_schedule
self.dist_shift = None
if distribution_shift_options is not None:
self.dist_shift = self._create_dist_shift(distribution_shift_options)
# Sampling dist_shift: separate config for inference-time schedule
if sampling_distribution_shift_options is not None:
self.sampling_dist_shift = self._create_dist_shift(sampling_distribution_shift_options)
else:
# Default: seq_len-invariant LogSNR shift matching legacy log_snr_sampling=True
self.sampling_dist_shift = LogSNRShift(rate=0, anchor_logsnr=-6.2, logsnr_end=2.0)
@staticmethod
def _create_dist_shift(options: dict):
"""Create a distribution shift object from config options."""
dist_shift_type = options.get("type", "full")
dist_shift_kwargs = {k: v for k, v in options.items() if k != "type"}
if dist_shift_type == "none":
return IdentityDistributionShift()
elif dist_shift_type == "flux":
return FluxDistributionShift(**dist_shift_kwargs)
elif dist_shift_type == "full":
return DistributionShift(**dist_shift_kwargs)
elif dist_shift_type == "logsnr":
return LogSNRShift(**dist_shift_kwargs)
else:
raise ValueError(f"Unknown distribution shift type: {dist_shift_type}. Expected 'none', 'flux', 'full', or 'logsnr'.")
def get_conditioning_inputs(self, conditioning_tensors: tp.Dict[str, tp.Any], negative=False):
cross_attention_input = None
cross_attention_masks = None
global_cond = None
input_concat_cond = None
prepend_cond = None
prepend_cond_mask = None
local_add_cond = None
modular_local_cond = None
if len(self.cross_attn_cond_ids) > 0:
# Concatenate all cross-attention inputs over the sequence dimension
# Assumes that the cross-attention inputs are of shape (batch, seq, channels)
cross_attention_input = []
cross_attention_masks = []
for key in self.cross_attn_cond_ids:
cross_attn_in, cross_attn_mask = conditioning_tensors[key]
# Add sequence dimension if it's not there
if len(cross_attn_in.shape) == 2:
cross_attn_in = cross_attn_in.unsqueeze(1)
cross_attn_mask = cross_attn_mask.unsqueeze(1)
cross_attention_input.append(cross_attn_in)
cross_attention_masks.append(cross_attn_mask)
cross_attention_input = torch.cat(cross_attention_input, dim=1)
cross_attention_masks = torch.cat(cross_attention_masks, dim=1)
if len(self.global_cond_ids) > 0:
# Concatenate all global conditioning inputs over the channel dimension
# Assumes that the global conditioning inputs are of shape (batch, channels)
global_conds = []
for key in self.global_cond_ids:
global_cond_input = conditioning_tensors[key][0]
global_conds.append(global_cond_input)
# Concatenate over the channel dimension
global_cond = torch.cat(global_conds, dim=-1)
if len(global_cond.shape) == 3:
global_cond = global_cond.squeeze(1)
if len(self.input_concat_ids) > 0:
# Concatenate all input concat conditioning inputs over the channel dimension
# Assumes that the input concat conditioning inputs are of shape (batch, channels, seq)
input_concat_cond = torch.cat([conditioning_tensors[key][0] for key in self.input_concat_ids], dim=1)
if len(self.local_add_cond_ids) > 0:
# Concatenate all local conditioning inputs over the channel dimension
# Assumes that the local conditioning inputs are of shape (batch, channels, seq)
local_add_cond = torch.cat([conditioning_tensors[key][0] for key in self.local_add_cond_ids], dim=1)
if len(self.modular_local_cond_ids) > 0:
# Keep modular local conditioning as a dict of tensors (not concatenated)
# Each tensor is of shape (batch, channels, seq)
modular_local_cond = {}
for key in self.modular_local_cond_ids:
if key in conditioning_tensors:
modular_local_cond[key] = conditioning_tensors[key][0]
# Only set if we have any conditioning
if len(modular_local_cond) == 0:
modular_local_cond = None
if len(self.prepend_cond_ids) > 0:
# Concatenate all prepend conditioning inputs over the sequence dimension
# Assumes that the prepend conditioning inputs are of shape (batch, seq, channels)
prepend_conds = []
prepend_cond_masks = []
for key in self.prepend_cond_ids:
prepend_cond_input, prepend_cond_mask = conditioning_tensors[key]
prepend_conds.append(prepend_cond_input)
prepend_cond_masks.append(prepend_cond_mask)
prepend_cond = torch.cat(prepend_conds, dim=1)
prepend_cond_mask = torch.cat(prepend_cond_masks, dim=1)
if negative:
return {
"negative_cross_attn_cond": cross_attention_input,
"negative_cross_attn_mask": cross_attention_masks,
"negative_global_cond": global_cond,
"negative_input_concat_cond": input_concat_cond
}
else:
return {
"cross_attn_cond": cross_attention_input,
"cross_attn_mask": cross_attention_masks,
"global_cond": global_cond,
"input_concat_cond": input_concat_cond,
"local_add_cond": local_add_cond,
"modular_local_cond": modular_local_cond,
"prepend_cond": prepend_cond,
"prepend_cond_mask": prepend_cond_mask
}
def forward(self, x: torch.Tensor, t: torch.Tensor, cond: tp.Dict[str, tp.Any], **kwargs):
return self.model(x, t, **self.get_conditioning_inputs(cond), **kwargs)
class DiTWrapper(nn.Module):
def __init__(
self,
diffusion_objective: str,
*args,
**kwargs
):
super().__init__()
self.diffusion_objective = diffusion_objective
self.model = DiffusionTransformer(diffusion_objective=diffusion_objective, *args, **kwargs)
def forward(self,
x,
t,
cross_attn_cond=None,
cross_attn_mask=None,
negative_cross_attn_cond=None,
negative_cross_attn_mask=None,
input_concat_cond=None,
local_add_cond=None,
negative_input_concat_cond=None,
global_cond=None,
negative_global_cond=None,
prepend_cond=None,
prepend_cond_mask=None,
cfg_scale=1.0,
cfg_dropout_prob: float = 0.0,
batch_cfg: bool = True,
rescale_cfg: bool = False,
scale_phi: float = 0.0,
**kwargs):
assert batch_cfg, "batch_cfg must be True for DiTWrapper"
#assert negative_input_concat_cond is None, "negative_input_concat_cond is not supported for DiTWrapper"
return self.model(
x,
t,
cross_attn_cond=cross_attn_cond,
cross_attn_cond_mask=cross_attn_mask,
negative_cross_attn_cond=negative_cross_attn_cond,
negative_cross_attn_mask=negative_cross_attn_mask,
input_concat_cond=input_concat_cond,
prepend_cond=prepend_cond,
prepend_cond_mask=prepend_cond_mask,
cfg_scale=cfg_scale,
cfg_dropout_prob=cfg_dropout_prob,
scale_phi=scale_phi,
global_embed=global_cond,
local_add_cond=local_add_cond,
**kwargs)
@@ -0,0 +1,643 @@
import typing as tp
import math
import torch
from einops import rearrange
from torch import nn
from torch.nn import functional as F
from .blocks import FourierFeatures, ExpoFourierFeatures
from .transformer import ContinuousTransformer
from .lora import LoRAParametrization, set_lora_strength, has_lora, enable_lora, disable_lora, filter_lora_layers
class DiffusionTransformer(nn.Module):
def __init__(self,
io_channels=32,
patch_size=1,
embed_dim=768,
cond_token_dim=0,
project_cond_tokens=True,
global_cond_dim=0,
project_global_cond=True,
input_concat_dim=0,
prepend_cond_dim=0,
depth=12,
num_heads=8,
transformer_type: tp.Literal["continuous_transformer", "mm_transformer"] = "continuous_transformer",
global_cond_type: tp.Literal["prepend", "adaLN"] = "prepend",
timestep_cond_type: tp.Literal["global", "input_concat"] = "global",
timestep_embed_dim=None,
diffusion_objective: tp.Literal["v", "rectified_flow", "rf_denoiser"] = "v",
timestep_features_type: tp.Literal["learned", "expo"] = "learned",
timestep_features_dim = 256,
timestep_features_logsnr: bool = False,
modular_local_cond_configs = None,
**kwargs):
super().__init__()
self.cond_token_dim = cond_token_dim
# Timestep embeddings
self.timestep_cond_type = timestep_cond_type
self.timestep_features_logsnr = timestep_features_logsnr
timestep_features_dim = timestep_features_dim
if timestep_features_type == "expo":
self.timestep_features = ExpoFourierFeatures(timestep_features_dim, 0.5, 10000.0)
else:
self.timestep_features = FourierFeatures(1, timestep_features_dim)
if timestep_cond_type == "global":
timestep_embed_dim = embed_dim
elif timestep_cond_type == "input_concat":
assert timestep_embed_dim is not None, "timestep_embed_dim must be specified if timestep_cond_type is input_concat"
input_concat_dim += timestep_embed_dim
self.to_timestep_embed = nn.Sequential(
nn.Linear(timestep_features_dim, timestep_embed_dim, bias=True),
nn.SiLU(),
nn.Linear(timestep_embed_dim, timestep_embed_dim, bias=True),
)
self.diffusion_objective = diffusion_objective
if cond_token_dim > 0:
# Conditioning tokens
cond_embed_dim = cond_token_dim if not project_cond_tokens else embed_dim
self.to_cond_embed = nn.Sequential(
nn.Linear(cond_token_dim, cond_embed_dim, bias=False),
nn.SiLU(),
nn.Linear(cond_embed_dim, cond_embed_dim, bias=False)
)
else:
cond_embed_dim = 0
if global_cond_dim > 0:
# Global conditioning
global_embed_dim = global_cond_dim if not project_global_cond else embed_dim
self.to_global_embed = nn.Sequential(
nn.Linear(global_cond_dim, global_embed_dim, bias=False),
nn.SiLU(),
nn.Linear(global_embed_dim, global_embed_dim, bias=False)
)
if prepend_cond_dim > 0:
# Prepend conditioning
self.to_prepend_embed = nn.Sequential(
nn.Linear(prepend_cond_dim, embed_dim, bias=False),
nn.SiLU(),
nn.Linear(embed_dim, embed_dim, bias=False)
)
self.input_concat_dim = input_concat_dim
dim_in = io_channels + self.input_concat_dim
self.patch_size = patch_size
# Transformer
self.transformer_type = transformer_type
self.global_cond_type = global_cond_type
transformer_dim_out = io_channels * patch_size
if self.transformer_type == "continuous_transformer":
global_dim = None
if self.global_cond_type == "adaLN":
# The global conditioning is projected to the embed_dim already at this point
global_dim = embed_dim
self.transformer = ContinuousTransformer(
dim=embed_dim,
depth=depth,
dim_heads=embed_dim // num_heads,
dim_in=dim_in * patch_size,
dim_out=transformer_dim_out,
cross_attend = cond_token_dim > 0,
cond_token_dim = cond_embed_dim,
global_cond_dim=global_dim,
modular_local_cond_configs=modular_local_cond_configs,
**kwargs
)
else:
raise ValueError(f"Unknown transformer type: {self.transformer_type}")
self.preprocess_conv = nn.Conv1d(dim_in, dim_in, 1, bias=False)
nn.init.zeros_(self.preprocess_conv.weight)
self.postprocess_conv = nn.Conv1d(io_channels, io_channels, 1, bias=False)
nn.init.zeros_(self.postprocess_conv.weight)
# Fixed logsnr normalization range: maps logsnr to [0, 1] preserving direction (t=0→0, t=1→1)
_LOGSNR_MIN = -12.0
_LOGSNR_MAX = 5.0
_LOGSNR_RANGE = _LOGSNR_MAX - _LOGSNR_MIN
def _t_to_logsnr_cond(self, t: torch.Tensor) -> torch.Tensor:
"""Convert t to normalized logsnr in [0, 1] for timestep conditioning.
Maps t through logsnr = log((1-t)/t), clamps to fixed range,
then normalizes to [0, 1] preserving direction (t=0→0, t=1→1).
"""
t_clamped = t.float().clamp(1e-7, 1 - 1e-7)
logsnr = torch.log((1 - t_clamped) / t_clamped)
logsnr = logsnr.clamp(self._LOGSNR_MIN, self._LOGSNR_MAX)
return ((self._LOGSNR_MAX - logsnr) / self._LOGSNR_RANGE).to(t.dtype)
def _call_transformer(self, x, *, prepend_inputs=None, cross_attn_cond=None,
mask=None, prepend_mask=None, return_info=False,
exit_layer_ix=None, local_add_cond=None,
modular_local_cond=None, padding_mask=None,
extra_args=None, **kwargs):
"""Helper method to call transformer and handle early exit logic."""
output = self.transformer(x, prepend_embeds=prepend_inputs, context=cross_attn_cond,
return_info=return_info, exit_layer_ix=exit_layer_ix,
local_add_cond=local_add_cond, modular_local_cond=modular_local_cond,
padding_mask=padding_mask,
**(extra_args or {}), **kwargs)
if return_info:
output, info = output
# Avoid postprocessing on early exit
if exit_layer_ix is not None:
if return_info:
return output, info
else:
return output
return (output, info) if return_info and 'info' in locals() else output
def _forward(
self,
x,
t,
mask=None,
cross_attn_cond=None,
cross_attn_cond_mask=None,
input_concat_cond=None,
local_add_cond=None,
modular_local_cond=None,
global_embed=None,
prepend_cond=None,
prepend_cond_mask=None,
padding_mask=None,
return_info=False,
exit_layer_ix=None,
**kwargs):
if cross_attn_cond is not None:
cross_attn_cond = self.to_cond_embed(cross_attn_cond)
if global_embed is not None:
# Project the global conditioning to the embedding dimension
global_embed = self.to_global_embed(global_embed)
prepend_inputs = None
prepend_mask = None
prepend_length = 0
if prepend_cond is not None:
# Project the prepend conditioning to the embedding dimension
prepend_cond = self.to_prepend_embed(prepend_cond)
prepend_inputs = prepend_cond
if prepend_cond_mask is not None:
prepend_mask = prepend_cond_mask
prepend_length = prepend_cond.shape[1]
if input_concat_cond is not None:
# Interpolate input_concat_cond to the same length as x
if input_concat_cond.shape[2] != x.shape[2]:
input_concat_cond = F.interpolate(input_concat_cond, (x.shape[2], ), mode='nearest')
x = torch.cat([x, input_concat_cond], dim=1)
if local_add_cond is not None:
local_add_cond = rearrange(local_add_cond, "b c t -> b t c")
# Rearrange modular_local_cond tensors
if modular_local_cond is not None:
modular_local_cond = {
k: rearrange(v, "b c t -> b t c")
for k, v in modular_local_cond.items()
}
# Get the batch of timestep embeddings
t_cond = self._t_to_logsnr_cond(t) if self.timestep_features_logsnr else t
# Convert to model dtype for linear layers (t itself is kept in float32 for precision)
# x has already been converted to model dtype in the outer forward() method
t_cond = t_cond.to(x.dtype)
timestep_embed = self.to_timestep_embed(self.timestep_features(t_cond[:, None])) # (b, embed_dim)
# Timestep embedding is considered a global embedding. Add to the global conditioning if it exists
if self.timestep_cond_type == "global":
if global_embed is not None:
global_embed = global_embed + timestep_embed
else:
global_embed = timestep_embed
elif self.timestep_cond_type == "input_concat":
x = torch.cat([x, timestep_embed.unsqueeze(2).expand(-1, -1, x.shape[2])], dim=1)
# Add the global_embed to the prepend inputs if there is no global conditioning support in the transformer
if self.global_cond_type == "prepend" and global_embed is not None:
if prepend_inputs is None:
# Prepend inputs are just the global embed, and the mask is all ones
prepend_inputs = global_embed.unsqueeze(1)
prepend_mask = torch.ones((x.shape[0], 1), device=x.device, dtype=torch.bool)
else:
# Prepend inputs are the prepend conditioning + the global embed
prepend_inputs = torch.cat([prepend_inputs, global_embed.unsqueeze(1)], dim=1)
prepend_mask = torch.cat([prepend_mask, torch.ones((x.shape[0], 1), device=x.device, dtype=torch.bool)], dim=1)
prepend_length = prepend_inputs.shape[1]
x = self.preprocess_conv(x) + x
x = rearrange(x, "b c t -> b t c")
extra_args = {}
if self.global_cond_type == "adaLN":
extra_args["global_cond"] = global_embed
if self.patch_size > 1:
x = rearrange(x, "b (t p) c -> b t (c p)", p=self.patch_size)
result = self._call_transformer(
x,
prepend_inputs=prepend_inputs,
cross_attn_cond=cross_attn_cond,
mask=mask,
prepend_mask=prepend_mask,
return_info=return_info,
exit_layer_ix=exit_layer_ix,
local_add_cond=local_add_cond,
modular_local_cond=modular_local_cond,
padding_mask=padding_mask,
extra_args=extra_args,
**kwargs,
)
# Handle early exit (result contains both output and info)
if exit_layer_ix is not None:
return result
output = result[0] if return_info else result
if return_info:
info = result[1]
output = rearrange(output, "b t c -> b c t")[:,:,prepend_length:]
if self.patch_size > 1:
output = rearrange(output, "b (c p) t -> b c (t p)", p=self.patch_size)
output = self.postprocess_conv(output) + output
if return_info:
return output, info
return output
def apg_project(self, v0, v1, padding_mask=None):
"""
Project v0 into components parallel and orthogonal to v1.
Args:
v0: Tensor to project (B, C, T)
v1: Reference direction (B, C, T)
padding_mask: Optional mask (B, T) where True = valid, False = padding.
If provided, only valid positions contribute to the projection.
"""
dtype = v0.dtype
v0, v1 = v0.float(), v1.float()
if padding_mask is not None:
# Expand mask to match tensor shape: (B, T) -> (B, 1, T)
mask = padding_mask.unsqueeze(1).float()
# Zero out padding positions for projection computation
v0_masked = v0 * mask
v1_masked = v1 * mask
# Normalize only over valid positions
v1_norm = v1_masked.norm(dim=[-1, -2], keepdim=True).clamp(min=1e-8)
v1_normalized = v1_masked / v1_norm
# Compute projection using masked values
v0_parallel = (v0_masked * v1_normalized).sum(dim=[-1, -2], keepdim=True) * v1_normalized
# Orthogonal component: subtract parallel from original (not masked) v0
# but apply mask to ensure padding stays zero
v0_orthogonal = (v0 - (v0 * v1_normalized).sum(dim=[-1, -2], keepdim=True) * v1_normalized) * mask
else:
v1 = torch.nn.functional.normalize(v1, dim=[-1, -2])
v0_parallel = (v0 * v1).sum(dim=[-1, -2], keepdim=True) * v1
v0_orthogonal = v0 - v0_parallel
return v0_parallel.to(dtype), v0_orthogonal.to(dtype)
def forward(
self,
x,
t,
cross_attn_cond=None,
cross_attn_cond_mask=None,
negative_cross_attn_cond=None,
negative_cross_attn_mask=None,
input_concat_cond=None,
local_add_cond=None,
modular_local_cond=None,
global_embed=None,
negative_global_embed=None,
prepend_cond=None,
prepend_cond_mask=None,
padding_mask=None,
cfg_scale=1.0,
cfg_dropout_prob=0.0,
cfg_interval = (0, 1),
lora_interval = (0, 1),
lora_layer_filter = "",
lora_configs = None,
causal=False,
scale_phi=0.0,
cfg_norm_threshold=0.0,
apg_scale=1.0,
mask=None,
return_info=False,
exit_layer_ix=None,
**kwargs):
assert not causal, "Causal mode is not supported for DiffusionTransformer"
model_dtype = next(self.parameters()).dtype
x = x.to(model_dtype)
# Keep t in float32: the logsnr transform log((1-t)/t) amplifies bf16
# quantization error ~380x near t=1, causing catastrophic conditioning errors.
# t is a 1D batch-size tensor so float32 has zero memory impact.
t = t.float()
if cross_attn_cond is not None:
cross_attn_cond = cross_attn_cond.to(model_dtype)
if negative_cross_attn_cond is not None:
negative_cross_attn_cond = negative_cross_attn_cond.to(model_dtype)
if input_concat_cond is not None:
input_concat_cond = input_concat_cond.to(model_dtype)
if local_add_cond is not None:
local_add_cond = local_add_cond.to(model_dtype)
if modular_local_cond is not None:
modular_local_cond = {k: v.to(model_dtype) for k, v in modular_local_cond.items()}
if global_embed is not None:
global_embed = global_embed.to(model_dtype)
if negative_global_embed is not None:
negative_global_embed = negative_global_embed.to(model_dtype)
if prepend_cond is not None:
prepend_cond = prepend_cond.to(model_dtype)
if cross_attn_cond_mask is not None:
cross_attn_cond_mask = cross_attn_cond_mask.bool()
cross_attn_cond_mask = None # Temporarily disabling conditioning masks due to kernel issue for flash attention
if prepend_cond_mask is not None:
prepend_cond_mask = prepend_cond_mask.bool()
# Early exit bypasses CFG processing
if exit_layer_ix is not None:
assert self.transformer_type == "continuous_transformer", "exit_layer_ix is only supported for continuous_transformer"
return self._forward(
x,
t,
cross_attn_cond=cross_attn_cond,
cross_attn_cond_mask=cross_attn_cond_mask,
input_concat_cond=input_concat_cond,
local_add_cond=local_add_cond,
modular_local_cond=modular_local_cond,
global_embed=global_embed,
prepend_cond=prepend_cond,
prepend_cond_mask=prepend_cond_mask,
padding_mask=padding_mask,
mask=mask,
return_info=return_info,
exit_layer_ix=exit_layer_ix,
**kwargs
)
# CFG dropout
if cfg_dropout_prob > 0.0 and cfg_scale == 1.0:
if cross_attn_cond is not None:
null_embed = torch.zeros_like(cross_attn_cond, device=cross_attn_cond.device)
dropout_mask = torch.bernoulli(torch.full((cross_attn_cond.shape[0], 1, 1), cfg_dropout_prob, device=cross_attn_cond.device)).to(torch.bool)
cross_attn_cond = torch.where(dropout_mask, null_embed, cross_attn_cond)
if prepend_cond is not None:
null_embed = torch.zeros_like(prepend_cond, device=prepend_cond.device)
dropout_mask = torch.bernoulli(torch.full((prepend_cond.shape[0], 1, 1), cfg_dropout_prob, device=prepend_cond.device)).to(torch.bool)
prepend_cond = torch.where(dropout_mask, null_embed, prepend_cond)
if self.diffusion_objective == "v":
sigma = torch.sin(t * math.pi / 2)
alpha = torch.cos(t * math.pi / 2)
elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
sigma = t
# LoRA interval
if has_lora(self):
if lora_configs is not None:
# Multi-LoRA: per-LoRA interval and layer filter
for lora_config in lora_configs:
idx = lora_config["lora_index"]
interval = lora_config.get("interval", (0, 1))
layer_filter = lora_config.get("layer_filter", "")
if interval[0] <= sigma[0] <= interval[1]:
enable_lora(self, lora_index=idx)
filter_lora_layers(self, layer_filter, lora_index=idx)
else:
disable_lora(self, lora_index=idx)
else:
# Legacy single-LoRA path
if lora_interval[0] <= sigma[0] <= lora_interval[1]:
enable_lora(self)
filter_lora_layers(self, lora_layer_filter)
else:
disable_lora(self)
if cfg_scale != 1.0 and (cross_attn_cond is not None or prepend_cond is not None) and (cfg_interval[0] <= sigma[0] <= cfg_interval[1]):
# Classifier-free guidance
# Concatenate conditioned and unconditioned inputs on the batch dimension
batch_inputs = torch.cat([x, x], dim=0)
batch_timestep = torch.cat([t, t], dim=0)
if global_embed is not None:
batch_global_cond = torch.cat([global_embed, global_embed], dim=0)
else:
batch_global_cond = None
if input_concat_cond is not None:
batch_input_concat_cond = torch.cat([input_concat_cond, input_concat_cond], dim=0)
else:
batch_input_concat_cond = None
if local_add_cond is not None:
batch_local_add_cond = torch.cat([local_add_cond, local_add_cond], dim=0)
else:
batch_local_add_cond = None
if modular_local_cond is not None:
batch_modular_local_cond = {k: torch.cat([v, v], dim=0) for k, v in modular_local_cond.items()}
else:
batch_modular_local_cond = None
batch_cond = None
batch_cond_masks = None
# Handle CFG for cross-attention conditioning
if cross_attn_cond is not None:
null_embed = torch.zeros_like(cross_attn_cond, device=cross_attn_cond.device)
# For negative cross-attention conditioning, replace the null embed with the negative cross-attention conditioning
if negative_cross_attn_cond is not None:
# If there's a negative cross-attention mask, set the masked tokens to the null embed
if negative_cross_attn_mask is not None:
negative_cross_attn_mask = negative_cross_attn_mask.to(torch.bool).unsqueeze(2)
negative_cross_attn_cond = torch.where(negative_cross_attn_mask, negative_cross_attn_cond, null_embed)
batch_cond = torch.cat([cross_attn_cond, negative_cross_attn_cond], dim=0)
else:
batch_cond = torch.cat([cross_attn_cond, null_embed], dim=0)
if cross_attn_cond_mask is not None:
batch_cond_masks = torch.cat([cross_attn_cond_mask, cross_attn_cond_mask], dim=0)
batch_prepend_cond = None
batch_prepend_cond_mask = None
if prepend_cond is not None:
null_embed = torch.zeros_like(prepend_cond, device=prepend_cond.device)
batch_prepend_cond = torch.cat([prepend_cond, null_embed], dim=0)
if prepend_cond_mask is not None:
batch_prepend_cond_mask = torch.cat([prepend_cond_mask, prepend_cond_mask], dim=0)
if mask is not None:
batch_masks = torch.cat([mask, mask], dim=0)
else:
batch_masks = None
if padding_mask is not None:
batch_padding_mask = torch.cat([padding_mask, padding_mask], dim=0)
else:
batch_padding_mask = None
batch_output = self._forward(
batch_inputs,
batch_timestep,
cross_attn_cond=batch_cond,
cross_attn_cond_mask=batch_cond_masks,
mask = batch_masks,
input_concat_cond = batch_input_concat_cond,
local_add_cond = batch_local_add_cond,
modular_local_cond=batch_modular_local_cond,
global_embed = batch_global_cond,
prepend_cond = batch_prepend_cond,
prepend_cond_mask = batch_prepend_cond_mask,
padding_mask = batch_padding_mask,
return_info = return_info,
**kwargs)
if return_info:
batch_output, info = batch_output
cond_output, uncond_output = torch.chunk(batch_output, 2, dim=0)
if self.diffusion_objective == "v":
cond_denoised = x * alpha[:, None, None] - cond_output * sigma[:, None, None]
uncond_denoised = x * alpha[:, None, None] - uncond_output * sigma[:, None, None]
elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
cond_denoised = x - cond_output * sigma[:, None, None]
uncond_denoised = x - uncond_output * sigma[:, None, None]
diff = cond_denoised - uncond_denoised
if cfg_norm_threshold > 0:
if padding_mask is not None:
# Only compute norm over valid positions
mask = padding_mask.unsqueeze(1).float() # (B, 1, T)
diff_masked = diff * mask
diff_norm = diff_masked.norm(p=2, dim=[-1, -2], keepdim=True)
else:
diff_norm = diff.norm(p=2, dim=[-1, -2], keepdim=True)
scale_factor = torch.minimum(torch.ones_like(diff), cfg_norm_threshold / diff_norm)
diff *= scale_factor
if apg_scale == 0.0:
# Vanilla CFG: use full diff
cfg_diff = diff
elif apg_scale == 1.0:
# Full APG: use only orthogonal component
_, diff_orthogonal = self.apg_project(diff, cond_denoised, padding_mask=padding_mask)
cfg_diff = diff_orthogonal
else:
# Blended APG: interpolate between full diff and orthogonal
diff_parallel, diff_orthogonal = self.apg_project(diff, cond_denoised, padding_mask=padding_mask)
cfg_diff = apg_scale * diff_orthogonal + (1 - apg_scale) * diff
cfg_denoised = cond_denoised + (cfg_scale - 1) * cfg_diff
if self.diffusion_objective == "v":
output = (x * alpha[:, None, None] - cfg_denoised) / sigma[:, None, None]
elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
output = (x - cfg_denoised) / sigma[:, None, None]
# CFG Rescale
if scale_phi != 0.0:
cond_out_std = cond_output.std(dim=1, keepdim=True)
out_cfg_std = output.std(dim=1, keepdim=True)
output = scale_phi * (output * (cond_out_std/out_cfg_std)) + (1-scale_phi) * output
if return_info:
info["uncond_output"] = uncond_output
return output, info
return output
else:
return self._forward(
x,
t,
cross_attn_cond=cross_attn_cond,
cross_attn_cond_mask=cross_attn_cond_mask,
input_concat_cond=input_concat_cond,
local_add_cond=local_add_cond,
modular_local_cond=modular_local_cond,
global_embed=global_embed,
prepend_cond=prepend_cond,
prepend_cond_mask=prepend_cond_mask,
padding_mask=padding_mask,
mask=mask,
return_info=return_info,
**kwargs
)
@@ -0,0 +1,242 @@
import random
import torch
from enum import Enum
from typing import List, Optional, Tuple
class MaskType(Enum):
RANDOM_SEGMENTS = 0 # Legacy: uncontrolled ratio, overlapping segments
FULL_MASK = 1
CAUSAL_MASK = 2
RANDOM_SPANS = 3 # New: explicit ratio control, non-overlapping spans
def _generate_random_spans_mask(
real_sequence_length: int,
sequence_length: int,
device: torch.device,
max_spans: int = 4,
mask_ratio_range: Tuple[float, float] = (0.2, 1.0),
span_count_weights: Optional[List[float]] = None,
) -> torch.Tensor:
"""
Generate a mask with 1-N non-overlapping contiguous spans covering a target ratio.
Samples a target mask ratio from U[mask_ratio_range], then allocates that budget
across a variable number of spans with Dirichlet-like random allocation.
Args:
real_sequence_length: Number of valid (non-padding) tokens.
sequence_length: Total sequence length including padding.
device: Torch device.
max_spans: Maximum number of masked spans.
mask_ratio_range: (lo, hi) range for uniform sampling of target mask ratio.
span_count_weights: Weights for sampling number of spans [1..max_spans].
Defaults to geometric decay [4, 2, 1, 1] (biased toward fewer spans).
"""
item_mask = torch.ones((1, 1, sequence_length), device=device, dtype=torch.float32)
if real_sequence_length == 0:
return item_mask
# 1. Sample target mask ratio
lo, hi = mask_ratio_range
target_ratio = random.uniform(lo, hi)
target_masked_tokens = max(1, int(target_ratio * real_sequence_length))
# 2. Sample number of spans (biased toward fewer spans)
if span_count_weights is None:
span_count_weights = [4, 2, 1, 1]
# Truncate/pad weights to max_spans
weights = span_count_weights[:max_spans]
while len(weights) < max_spans:
weights.append(weights[-1])
num_spans = random.choices(range(1, max_spans + 1), weights=weights, k=1)[0]
num_spans = min(num_spans, real_sequence_length)
# 3. Allocate target tokens across spans (Dirichlet-like)
raw_weights = [random.random() for _ in range(num_spans)]
total_weight = sum(raw_weights)
span_lengths = [max(1, int(w / total_weight * target_masked_tokens)) for w in raw_weights]
# Adjust to hit exact target
diff = target_masked_tokens - sum(span_lengths)
if diff > 0:
span_lengths[0] += diff
elif diff < 0:
for i in range(len(span_lengths)):
reduce = min(-diff, span_lengths[i] - 1)
span_lengths[i] -= reduce
diff += reduce
if diff >= 0:
break
# 4. Place spans without overlap (largest first for better packing)
span_lengths.sort(reverse=True)
placed_spans = []
for length in span_lengths:
length = min(length, real_sequence_length)
if length <= 0:
continue
max_start = real_sequence_length - length
if max_start < 0:
continue
# Try random placement avoiding overlaps
placed = False
for _ in range(50):
start = random.randint(0, max_start)
end = start + length
if not any(s < end and start < e for s, e in placed_spans):
placed_spans.append((start, end))
item_mask[:, :, start:end] = 0
placed = True
break
# Fallback: scan for first valid position
if not placed:
for start in range(max_start + 1):
end = start + length
if not any(s < end and start < e for s, e in placed_spans):
placed_spans.append((start, end))
item_mask[:, :, start:end] = 0
break
return item_mask
def random_inpaint_mask(
sequence: torch.Tensor,
padding_masks: torch.Tensor,
max_mask_segments: int = 10,
mask_type_probabilities: Optional[List[float]] = None,
mask_padding: bool = False,
force_mask_type: Optional[MaskType] = None,
# RANDOM_SPANS parameters
max_spans: int = 4,
mask_ratio_range: Tuple[float, float] = (0.2, 1.0),
span_count_weights: Optional[List[float]] = None,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Generates random inpainting masks for a batch of latent audio sequences.
The output inpainting mask has 0 where data should be inpainted, and 1 where data is provided.
Supports two segment masking modes:
- RANDOM_SEGMENTS (legacy): Uncontrolled mask ratio, overlapping segments allowed.
- RANDOM_SPANS (new): Explicit target mask ratio sampled from mask_ratio_range,
non-overlapping contiguous spans with controllable count distribution.
Args:
sequence: The input sequence tensor of shape (b, c, sequence_length).
padding_masks: A tensor of shape (b, sequence_length)
where 1 indicates real data latents and 0 indicates latents encoding silence padding.
max_mask_segments: (RANDOM_SEGMENTS only) Maximum number of segments.
mask_type_probabilities: Probabilities for each mask type. Length must match the
number of MaskType values used:
- 3 elements: [P(RANDOM_SEGMENTS), P(FULL_MASK), P(CAUSAL_MASK)]
- 4 elements: [P(RANDOM_SEGMENTS), P(FULL_MASK), P(CAUSAL_MASK), P(RANDOM_SPANS)]
If None, defaults to [0.1, 0.8, 0.1] (legacy 3-type behavior).
mask_padding: If True, zero out padding region in the inpaint mask for
RANDOM_SEGMENTS/RANDOM_SPANS/CAUSAL_MASK. Use when training with
attention masking for variable-length sequences.
max_spans: (RANDOM_SPANS only) Maximum number of masked spans.
mask_ratio_range: (RANDOM_SPANS only) (lo, hi) for uniform sampling of target mask ratio.
span_count_weights: (RANDOM_SPANS only) Weights for sampling number of spans [1..max_spans].
Returns:
A tuple containing:
- masked_sequence: The sequence with masks applied (original sequence where mask is 1,
and usually 0 or a placeholder where mask is 0).
- inpaint_mask: The generated inpainting mask tensor (0 for inpaint, 1 for keep).
"""
b, _, sequence_length = sequence.size()
# Skip probability validation when forcing a specific mask type
if force_mask_type is not None:
mask_types_to_sample = None
else:
if mask_type_probabilities is None:
mask_type_probabilities = [0.1, 0.8, 0.1]
num_probs = len(mask_type_probabilities)
# Determine which mask types are active based on probability list length
if num_probs == 3:
# Legacy: [RANDOM_SEGMENTS, FULL_MASK, CAUSAL_MASK]
mask_types_to_sample = [MaskType.RANDOM_SEGMENTS.value, MaskType.FULL_MASK.value, MaskType.CAUSAL_MASK.value]
elif num_probs == 4:
# Extended: [RANDOM_SEGMENTS, FULL_MASK, CAUSAL_MASK, RANDOM_SPANS]
mask_types_to_sample = [MaskType.RANDOM_SEGMENTS.value, MaskType.FULL_MASK.value, MaskType.CAUSAL_MASK.value, MaskType.RANDOM_SPANS.value]
else:
raise ValueError(
f"mask_type_probabilities must have 3 or 4 elements. Got {num_probs}."
)
if not torch.isclose(torch.tensor(sum(mask_type_probabilities)), torch.tensor(1.0)):
raise ValueError(
f"mask_type_probabilities must sum to 1.0. "
f"Current sum: {sum(mask_type_probabilities)}"
)
output_masks_list = []
for i in range(b):
padding_mask_single_item = padding_masks[i]
real_sequence_length = (padding_mask_single_item == 1).sum().item()
item_mask = torch.ones((1, 1, sequence_length), device=sequence.device, dtype=torch.float32)
if force_mask_type is not None:
current_mask_type = force_mask_type
else:
chosen_mask_value = random.choices(mask_types_to_sample, weights=mask_type_probabilities, k=1)[0]
current_mask_type = MaskType(chosen_mask_value)
if current_mask_type == MaskType.FULL_MASK:
item_mask = torch.zeros((1, 1, sequence_length), device=sequence.device, dtype=torch.float32)
elif real_sequence_length == 0:
pass # item_mask remains all ones
else:
if current_mask_type == MaskType.RANDOM_SEGMENTS:
# Legacy behavior: uncontrolled ratio, overlapping segments
num_segments = random.randint(1, max_mask_segments)
max_len_per_segment_calc = max(1, real_sequence_length // num_segments)
for _ in range(num_segments):
segment_length = random.randint(1, max_len_per_segment_calc)
if real_sequence_length - segment_length < 0:
continue
mask_start = random.randint(0, real_sequence_length - segment_length)
item_mask[:, :, mask_start : mask_start + segment_length] = 0
elif current_mask_type == MaskType.RANDOM_SPANS:
item_mask = _generate_random_spans_mask(
real_sequence_length=real_sequence_length,
sequence_length=sequence_length,
device=sequence.device,
max_spans=max_spans,
mask_ratio_range=mask_ratio_range,
span_count_weights=span_count_weights,
)
elif current_mask_type == MaskType.CAUSAL_MASK:
unmasked_prefix_len = random.randint(0, real_sequence_length)
if unmasked_prefix_len < real_sequence_length:
item_mask[:, :, unmasked_prefix_len:real_sequence_length] = 0
# When using attention masking for variable-length training, zero out padding
# so the model doesn't see it as provided context. Without attention masking,
# padding is handled via mask_loss_weight and should remain as provided input.
if mask_padding:
item_mask[:, :, real_sequence_length:] = 0
output_masks_list.append(item_mask)
final_inpaint_mask = torch.cat(output_masks_list, dim=0).to(sequence.device)
masked_sequence = sequence * final_inpaint_mask
return masked_sequence, final_inpaint_mask
@@ -0,0 +1,28 @@
from .model import LoRAParametrization, add_lora, default_lora_config, merge_lora, remove_lora, remove_lora_by_index, set_lora_strength
from .utils import (
apply_to_lora,
convert_lora_ckpt_to_safetensors,
disable_lora,
enable_lora,
filter_lora_layers,
get_lora_count,
has_lora,
get_bias_params,
get_lora_params,
get_lora_layers,
get_lora_state_dict,
load_lora_checkpoint,
load_multiple_lora,
name_is_lora,
remap_lora_state_dict,
save_lora_safetensors,
select_lora,
tie_weights,
untie_weights,
infer_global_rank,
cast_base_to_precision,
detect_dora_variant,
prepare_dora_state_dict,
resolve_adapter_type
)
from .loader import load_and_apply_loras
@@ -0,0 +1,112 @@
"""Helpers for loading pre-trained LoRA checkpoints onto a live model."""
import os
from functools import partial
import torch
from .model import LoRAParametrization, add_lora
from .utils import (
get_lora_layers,
infer_global_rank,
load_lora_checkpoint,
prepare_dora_state_dict,
remap_lora_state_dict,
resolve_adapter_type,
)
from ...verbose import vprint
def load_and_apply_loras(model, lora_ckpt_paths, model_type, svd_bases_path=None):
"""Load LoRA checkpoints from disk and attach them to `model`.
- Uses a two-pass approach: first resolves each LoRA's adapter type, then
loads SVD bases (only if any LoRA is -XS), then applies each LoRA.
- Handles legacy "dora" adapter_type via `resolve_adapter_type`.
- Only passes svd_bases to -XS LoRAs to avoid spurious "no -XS layers"
messages for non-XS LoRAs.
- Sets `model.use_lora = True` and `model.lora_names = [...]` as a
convenience for downstream UI code.
Args:
model: The loaded model. For `diffusion_cond` / `diffusion_cond_inpaint`
types, LoRAs are applied to `model.model` and `model.conditioner`;
otherwise they are applied to `model` directly.
lora_ckpt_paths: List of paths to `.ckpt` or `.safetensors` files.
model_type: String from the model config, e.g. "diffusion_cond".
svd_bases_path: Optional path to a precomputed SVD bases .pt file
(used only by -XS adapter types).
Returns:
List of display names (file stems) in the same order as the input paths.
"""
if not lora_ckpt_paths:
model.use_lora = False
model.lora_names = []
return []
is_cond = model_type in ("diffusion_cond", "diffusion_cond_inpaint")
# Pass 1: load each checkpoint and resolve adapter type.
lora_entries = [] # (path, state_dict, config_dict, adapter_type)
for i, lora_path in enumerate(lora_ckpt_paths):
print(f"Loading LoRA {i} from {lora_path}")
state_dict, config_dict = load_lora_checkpoint(lora_path)
adapter_type_raw = config_dict.get("adapter_type", "lora")
adapter_type = resolve_adapter_type(adapter_type_raw, state_dict)
if adapter_type != adapter_type_raw:
vprint(f"Resolved legacy '{adapter_type_raw}' -> {adapter_type}")
lora_entries.append((lora_path, state_dict, config_dict, adapter_type))
# Load SVD bases only if at least one LoRA is -XS.
model_svd_bases = None
cond_svd_bases = None
any_xs = any(t.endswith("-xs") for _, _, _, t in lora_entries)
if any_xs:
if svd_bases_path is not None:
vprint(f"Loading SVD bases from {svd_bases_path}")
svd_bases_all = torch.load(svd_bases_path, map_location="cpu", weights_only=True)
model_svd_bases = {k[len("model."):]: v for k, v in svd_bases_all.items() if k.startswith("model.")}
cond_svd_bases = {k[len("conditioner."):]: v for k, v in svd_bases_all.items() if k.startswith("conditioner.")}
vprint(f" model bases: {len(model_svd_bases)}, conditioner bases: {len(cond_svd_bases)}")
else:
print("WARNING: -XS adapter type present without svd_bases_path -- SVD will be computed on current device")
# Pass 2: apply each LoRA.
lora_names = []
for i, (lora_path, state_dict, config_dict, adapter_type) in enumerate(lora_entries):
rank = config_dict.get("rank", infer_global_rank(state_dict))
alpha = config_dict.get("alpha", rank)
include = config_dict.get("include", None)
exclude = config_dict.get("exclude", None)
is_xs = adapter_type.endswith("-xs")
lora_config = {
torch.nn.Linear: {
"weight": partial(LoRAParametrization.from_linear, rank=rank, lora_alpha=alpha, adapter_type=adapter_type, lora_index=i),
},
torch.nn.Conv1d: {
"weight": partial(LoRAParametrization.from_conv1d, rank=rank, lora_alpha=alpha, adapter_type=adapter_type, lora_index=i),
},
}
if is_cond:
add_lora(model.model, lora_config, include=include, exclude=exclude,
svd_bases=model_svd_bases if is_xs else None)
add_lora(model.conditioner, lora_config, include=include, exclude=exclude,
svd_bases=cond_svd_bases if is_xs else None)
else:
add_lora(model, lora_config, include=include, exclude=exclude,
svd_bases=model_svd_bases if is_xs else None)
prepare_dora_state_dict(state_dict)
remapped_sd = remap_lora_state_dict(state_dict, i)
if is_cond:
model.model.load_state_dict(remapped_sd, strict=False)
model.conditioner.load_state_dict(remapped_sd, strict=False)
else:
model.load_state_dict(remapped_sd, strict=False)
lora_names.append(os.path.splitext(os.path.basename(lora_path))[0])
vprint("lora layers:", len(get_lora_layers(model)))
model.use_lora = True
model.lora_names = lora_names
return lora_names
@@ -0,0 +1,471 @@
"""
References:
1) the official LoRA implementation released by Microsoft:
https://github.com/microsoft/LoRA/blob/main/loralib/layers.py
"""
import math
import re
from functools import partial
from itertools import product
import torch
import torch.nn.utils.parametrize as parametrize
from torch import nn
from ...verbose import vprint
def _canonicalize_svd_signs(U, Vh):
"""Enforce deterministic sign convention: largest-magnitude element of each U column is positive."""
max_abs_idx = U.abs().argmax(dim=0)
signs = U[max_abs_idx, torch.arange(U.shape[1])].sign()
signs[signs == 0] = 1
return U * signs.unsqueeze(0), Vh * signs.unsqueeze(1)
class LoRAParametrization(nn.Module):
def __init__(self, fan_in, fan_out, fan_in_fan_out=False, rank=4, lora_dropout_p=0.0, lora_alpha=1,
adapter_type="lora", W0=None, lora_index=0, svd_bases=None
):
super().__init__()
dtype = torch.get_default_dtype()
device = W0.device if W0 is not None else None
# if weight is stored as (fan_out, fan_in), the memory layout of A & B follows (W + BA)x
# otherwise, it's x(W + AB). This allows us to tie the weights between linear layers and embeddings
self.swap = (lambda x: (x[1], x[0])) if fan_in_fan_out else (lambda x: x)
self.lora_alpha, self.rank = lora_alpha, rank
self.scaling = lora_alpha / rank
self.lora_dropout = nn.Dropout(p=lora_dropout_p) if lora_dropout_p > 0 else lambda x: x
self.dropout_fn = self._dropout if lora_dropout_p > 0 else lambda x: x
self.register_buffer("lora_dropout_mask", torch.ones(self.swap((1, fan_in)), dtype=dtype, device=device))
self.register_buffer("lora_strength", torch.tensor(1.0, dtype=dtype, device=device), persistent=False)
self.adapter_type = adapter_type
self.lora_index = lora_index
if self.adapter_type == "lora":
self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
self.forward_fn = self.lora_forward
self._adapter_forward_fn = self.forward_fn
elif self.adapter_type in ("dora-rows", "dora"):
# DoRA-rows: magnitude per row (per-output-neuron, dim=1) - paper-correct variant
# "dora" maps here as the default for backward compat
self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
self._norm_dim = 1
if W0 is not None:
W0_2d = W0.view(W0.shape[0], -1)
self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
else:
self.magnitude = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
self.forward_fn = self.dora_forward
self._adapter_forward_fn = self.forward_fn
elif self.adapter_type == "dora-cols":
# DoRA-cols: magnitude per column (per-input-feature, dim=0) - legacy variant
self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
self._norm_dim = 0
if W0 is not None:
W0_2d = W0.view(W0.shape[0], -1)
self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
else:
self.magnitude = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
self.forward_fn = self.dora_forward
self._adapter_forward_fn = self.forward_fn
elif self.adapter_type == "bora":
# BoRA: bi-dimensional DoRA - magnitude on both row and column axes
self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
if W0 is not None:
W0_2d = W0.view(W0.shape[0], -1)
self.magnitude_r = nn.Parameter(W0_2d.norm(dim=1).to(dtype))
self.magnitude_c = nn.Parameter(W0_2d.norm(dim=0).to(dtype))
else:
self.magnitude_r = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
self.magnitude_c = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
self.forward_fn = self.bora_forward
self._adapter_forward_fn = self.forward_fn
elif self.adapter_type == "lora-xs":
# frozen bases U:(fan_out,r), V:(fan_in,r); tiny trainable core M:(r,r)
self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
if svd_bases is not None:
self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
elif W0 is not None:
W0_2d = W0.view(W0.shape[0], -1).cpu().float()
U_full, S, Vh_full = torch.linalg.svd(W0_2d, full_matrices=False)
U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
self.U.copy_(U_full[:, :self.rank].to(dtype))
self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
self.forward_fn = self.lora_xs_forward
self._adapter_forward_fn = self.forward_fn
elif self.adapter_type == "dora-rows-xs":
# DoRA-rows + LoRA-XS: frozen SVD bases + trainable M_xs core + row magnitude
self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
self._norm_dim = 1
if svd_bases is not None:
self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
if W0 is not None:
W0_2d = W0.view(W0.shape[0], -1)
if svd_bases is None:
U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
self.U.copy_(U_full[:, :self.rank].to(dtype))
self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
else:
self.magnitude = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
self.forward_fn = self.dora_xs_forward
self._adapter_forward_fn = self.forward_fn
elif self.adapter_type == "dora-cols-xs":
# DoRA-cols + LoRA-XS: frozen SVD bases + trainable M_xs core + column magnitude
self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
self._norm_dim = 0
if svd_bases is not None:
self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
if W0 is not None:
W0_2d = W0.view(W0.shape[0], -1)
if svd_bases is None:
U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
self.U.copy_(U_full[:, :self.rank].to(dtype))
self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
else:
self.magnitude = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
self.forward_fn = self.dora_xs_forward
self._adapter_forward_fn = self.forward_fn
elif self.adapter_type == "bora-xs":
# BoRA + LoRA-XS: frozen SVD bases + trainable M_xs core + row & column magnitudes
self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
if svd_bases is not None:
self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
if W0 is not None:
W0_2d = W0.view(W0.shape[0], -1)
if svd_bases is None:
U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
self.U.copy_(U_full[:, :self.rank].to(dtype))
self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
self.magnitude_r = nn.Parameter(W0_2d.norm(dim=1).to(dtype))
self.magnitude_c = nn.Parameter(W0_2d.norm(dim=0).to(dtype))
else:
self.magnitude_r = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
self.magnitude_c = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
self.forward_fn = self.bora_xs_forward
self._adapter_forward_fn = self.forward_fn
def lora_forward(self, W):
delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A)))).view(W.shape)
delta = self.scaling * self.lora_strength * delta
return (W + delta.to(W.dtype))
def dora_forward(self, W):
if self.lora_strength == 0:
return W
# work in 2D so norm/magnitude ops don't broadcast badly for Conv1d/Conv2d
# Upcast W to LoRA param dtype for stable norm computation, cast result back.
orig_shape = W.shape
W_2d = W.view(W.shape[0], -1).to(self.lora_A.dtype)
# low-rank update on the *direction*
delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A))))
V = W_2d + self.scaling * self.lora_strength * delta
# normalize along _norm_dim, then scale by per-element magnitude
V_hat = V / (V.norm(dim=self._norm_dim, keepdim=True) + 1e-12)
return (V_hat * self.magnitude.unsqueeze(self._norm_dim)).view(orig_shape).to(W.dtype)
def bora_forward(self, W):
if self.lora_strength == 0:
return W
orig_shape = W.shape
W_2d = W.view(W.shape[0], -1).to(self.lora_A.dtype)
delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A))))
V = W_2d + self.scaling * self.lora_strength * delta
# Row-normalize, then scale by row magnitudes
V_r = V / (V.norm(dim=1, keepdim=True) + 1e-12)
intermediate = self.magnitude_r.unsqueeze(1) * V_r
# Column-normalize, then scale by column magnitudes
H_c = intermediate / (intermediate.norm(dim=0, keepdim=True) + 1e-12)
return (H_c * self.magnitude_c.unsqueeze(0)).view(orig_shape).to(W.dtype)
def lora_xs_forward(self, W):
delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
delta = delta.view(W.shape)
return (W + (self.scaling * self.lora_strength * delta).to(W.dtype))
def dora_xs_forward(self, W):
if self.lora_strength == 0:
return W
orig_shape = W.shape
W_2d = W.view(W.shape[0], -1).to(self.U.dtype)
delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
V = W_2d + self.scaling * self.lora_strength * delta
V_hat = V / (V.norm(dim=self._norm_dim, keepdim=True) + 1e-12)
return (V_hat * self.magnitude.unsqueeze(self._norm_dim)).view(orig_shape).to(W.dtype)
def bora_xs_forward(self, W):
if self.lora_strength == 0:
return W
orig_shape = W.shape
W_2d = W.view(W.shape[0], -1).to(self.U.dtype)
delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
V = W_2d + self.scaling * self.lora_strength * delta
V_r = V / (V.norm(dim=1, keepdim=True) + 1e-12)
intermediate = self.magnitude_r.unsqueeze(1) * V_r
H_c = intermediate / (intermediate.norm(dim=0, keepdim=True) + 1e-12)
return (H_c * self.magnitude_c.unsqueeze(0)).view(orig_shape).to(W.dtype)
def forward(self, X):
return self.forward_fn(X)
def _dropout(self, A):
# to mimic the original implementation: A @ dropout(x), we do (A * dropout(ones)) @ x
return A * self.lora_dropout(self.lora_dropout_mask)
def disable_lora(self):
self.forward_fn = lambda x: x
def enable_lora(self):
self.forward_fn = self._adapter_forward_fn
@classmethod
def _get_original_weight(cls, layer, attr_name="weight"):
"""Get the true original weight, even when the layer is already parametrized."""
if hasattr(layer, 'parametrizations') and hasattr(layer.parametrizations, attr_name):
return getattr(layer.parametrizations, attr_name).original.detach()
return getattr(layer, attr_name).detach()
@classmethod
def from_linear(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
fan_out, fan_in = layer.weight.shape
W0 = cls._get_original_weight(layer)
return cls(
fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
W0=W0, svd_bases=svd_bases, **kwargs
)
@classmethod
def from_conv2d(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
fan_out, fan_in = layer.weight.view(layer.weight.shape[0], -1).shape
W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
return cls(
fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
W0=W0, svd_bases=svd_bases, **kwargs
)
@classmethod
def from_conv1d(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
fan_out, fan_in = layer.weight.view(layer.weight.shape[0], -1).shape
W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
return cls(
fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
W0=W0, svd_bases=svd_bases, **kwargs
)
@classmethod
def from_embedding(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
fan_in, fan_out = layer.weight.shape
W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
return cls(
fan_in, fan_out, fan_in_fan_out=True, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
W0=W0, svd_bases=svd_bases, **kwargs
)
default_lora_config = { # specify which layers to add lora to, by default only add to linear layers
nn.Linear: {
"weight": partial(LoRAParametrization.from_linear, rank=4),
},
}
# --- layer name filtering helpers ---
def _expand(s):
"""Expand bracket notation in layer filter strings, e.g. 'layers[0-5]' -> ['layers0', ..., 'layers5']."""
t = re.split(r'\[(\d+)-(\d+)\]', s)
if len(t) == 1:
return [s]
lits, starts, ends = t[0::3], t[1::3], t[2::3]
pools = []
for a, b in zip(starts, ends):
ai, bi = int(a), int(b)
step = 1 if bi >= ai else -1
pools.append([str(n) for n in range(ai, bi + step, step)])
out = []
for combo in product(*pools):
pieces = []
for i, lit in enumerate(lits):
pieces.append(lit)
if i < len(combo):
pieces.append(combo[i])
out.append(''.join(pieces))
return out
def _matches_any(name, patterns):
"""Check if name contains any pattern substring (with bracket expansion)."""
if not patterns:
return False
for pattern in patterns:
for expanded in _expand(pattern.strip()):
if expanded in name:
return True
return False
# --- core LoRA application ---
def _match_layer_type(layer, lora_config):
"""Find the matching lora_config key for a layer, using isinstance to handle ParametrizedLinear etc."""
for layer_type in lora_config:
if isinstance(layer, layer_type):
return layer_type
return None
def apply_lora(layer, register=True, merge=False, lora_config=default_lora_config):
"""add lora parametrization to a layer, designed to be used with model.apply"""
if register:
matched_type = _match_layer_type(layer, lora_config)
if matched_type is not None:
for attr_name, parametrization in lora_config[matched_type].items():
parametrize.register_parametrization(layer, attr_name, parametrization(layer), unsafe=True)
else: # this will remove all parametrizations, use with caution
if hasattr(layer, "parametrizations"):
for attr_name in list(layer.parametrizations.keys()):
parametrize.remove_parametrizations(layer, attr_name, leave_parametrized=merge)
def add_lora(model, lora_config=default_lora_config, include=None, exclude=None, svd_bases=None):
"""Add LoRA parametrization to layers in a model.
Args:
model: The model to add LoRA to.
lora_config: Dict mapping nn.Module types to parametrization configs.
include: Optional list of substrings. If provided, only modules whose
name contains at least one pattern get LoRA. Supports bracket
expansion (e.g. "layers[0-11]").
exclude: Optional list of substrings. Modules matching any pattern are
skipped even if they match an include pattern.
svd_bases: Optional dict of pre-computed SVD bases keyed by
"layer_name.weight". When provided, -XS adapter types use
these instead of computing SVD.
"""
if svd_bases is not None or include is not None or exclude is not None:
# Named-module path: iterate by name so we can look up SVD bases per layer
n_cached = 0
n_recomputed = 0
for name, module in model.named_modules():
matched_type = _match_layer_type(module, lora_config)
if matched_type is None:
continue
if include is not None and not _matches_any(name, include):
continue
if exclude is not None and _matches_any(name, exclude):
continue
for attr_name, parametrization_fn in lora_config[matched_type].items():
layer_bases = None
if svd_bases is not None:
bases_key = f"{name}.{attr_name}"
layer_bases = svd_bases.get(bases_key)
param = parametrization_fn(module, svd_bases=layer_bases)
parametrize.register_parametrization(module, attr_name, param, unsafe=True)
if hasattr(param, 'U'): # -XS layer that needs SVD bases
if layer_bases is not None:
n_cached += 1
else:
n_recomputed += 1
if n_cached > 0:
vprint(f"SVD bases: {n_cached} layers loaded from cache")
if n_recomputed > 0:
vprint(f"SVD bases: {n_recomputed} layers RECOMPUTED (missing from cache)")
if svd_bases is not None and n_cached == 0 and n_recomputed == 0:
vprint("SVD bases: provided but no -XS layers found")
else:
# Fast path: original behavior, no name inspection needed
model.apply(partial(apply_lora, lora_config=lora_config))
def add_lora_by_name(model, target_module_names, lora_config=default_lora_config, svd_bases=None):
"""Add LoRA to specific layers by name. Convenience wrapper around add_lora()."""
add_lora(model, lora_config=lora_config, include=target_module_names, svd_bases=svd_bases)
def merge_lora(model):
"""merge lora parametrization to all layers in a model. This will remove all parametrization"""
model.apply(partial(apply_lora, register=False, merge=True))
def remove_lora(model):
"""remove lora parametrization to all layers in a model. This will remove all parametrization"""
model.apply(partial(apply_lora, register=False, merge=False))
def remove_lora_by_index(model, lora_index):
"""Remove all LoRA parametrizations with a specific lora_index.
For modules where other LoRA entries remain, only the matching entry is deleted.
For modules where the removed entry was the only one, the parametrization is
removed entirely and the weight reverts to its original value.
"""
for name, mod in model.named_modules():
if not hasattr(mod, 'parametrizations'):
continue
for attr in list(mod.parametrizations.keys()):
params = mod.parametrizations[attr]
to_remove = [i for i, p in enumerate(params)
if isinstance(p, LoRAParametrization) and p.lora_index == lora_index]
if not to_remove:
continue
if len(params) == len(to_remove):
# All entries being removed — remove parametrization entirely
parametrize.remove_parametrizations(mod, attr, leave_parametrized=False)
else:
for i in reversed(to_remove):
del params[i]
def set_lora_strength(model, strength: float, lora_index=None):
"""Set lora strength. If lora_index is None, sets all LoRAs. If specified, sets only that index."""
strength = float(strength)
if lora_index is None:
for name, buf in model.named_buffers(recurse=True):
if name.endswith("lora_strength"):
buf.fill_(strength)
else:
for p in _iter_lora_params(model):
if p.lora_index == lora_index:
p.lora_strength.fill_(strength)
def _iter_lora_params(model):
for _, mod in model.named_modules():
plist = getattr(getattr(mod, "parametrizations", None), "weight", None)
if plist is None:
continue
for p in plist:
if isinstance(p, LoRAParametrization):
yield p
@@ -0,0 +1,420 @@
import json
import torch
from safetensors import safe_open
from safetensors.torch import save_file as _st_save_file
from torch import nn
from .model import LoRAParametrization, _expand
from ...verbose import vprint
def apply_to_lora(fn):
"""apply a function to LoRAParametrization layers, designed to be used with model.apply"""
def apply_fn(layer):
if isinstance(layer, LoRAParametrization):
fn(layer)
return apply_fn
def enable_lora(model, lora_index=None):
"""Enable LoRA layers. If lora_index is None, enables all. If specified, enables only that index."""
def _enable(layer):
if isinstance(layer, LoRAParametrization):
if lora_index is None or layer.lora_index == lora_index:
layer.enable_lora()
model.apply(_enable)
def disable_lora(model, lora_index=None):
"""Disable LoRA layers. If lora_index is None, disables all. If specified, disables only that index."""
def _disable(layer):
if isinstance(layer, LoRAParametrization):
if lora_index is None or layer.lora_index == lora_index:
layer.disable_lora()
model.apply(_disable)
def get_lora_layers(model):
layers = []
for name, m in model.named_modules():
plist = getattr(getattr(m, "parametrizations", None), "weight", None)
if plist is None:
continue
for p in plist:
if isinstance(p, LoRAParametrization):
layers.append((name, p))
return layers
def has_lora(model):
"""Return True if the model has at least one LoRAParametrization on any weight."""
return len(get_lora_layers(model))>=1
def filter_lora_layers(model, lora_layer_filter, lora_index=None):
"""Enable/disable LoRA layers by name filter. If lora_index is specified, only affects that index."""
lora_layer_filter = (lora_layer_filter or "").strip()
# lora_layer_filter: logical OR on comma separated substrings
# e.g. ".transformer.layers, .to_global_embed."
def is_filtered(layer_name):
filters = [x.lower().strip() for x in lora_layer_filter.split(",") if len(x)]
for f in filters:
for _f in _expand(f):
if _f in layer_name:
return True
return False
for name, p in get_lora_layers(model):
if lora_index is not None and p.lora_index != lora_index:
continue
if is_filtered(name):
p.disable_lora()
else:
p.enable_lora()
# ------------------- helper function for collecting parameters for training/saving -------------------
def name_is_lora(name):
return (
len(name.split(".")) >= 4
and (name.split(".")[-4]) == "parametrizations"
and name.split(".")[-1] in ["lora_A", "lora_B", "M_xs", "magnitude", "magnitude_r", "magnitude_c"]
)
def name_is_bias(name):
return name.split(".")[-1] == "bias"
def get_params_by_name(model, print_shapes=False, name_filter=None):
for n, p in model.named_parameters():
if name_filter is None or name_filter(n):
if print_shapes:
print(n, p.shape)
yield p
def get_lora_params(model, print_shapes=False):
return get_params_by_name(model, print_shapes=print_shapes, name_filter=name_is_lora)
def get_bias_params(model, print_shapes=False):
return get_params_by_name(model, print_shapes=print_shapes, name_filter=name_is_bias)
def get_lora_state_dict(model):
return {k: v for k, v in model.state_dict().items() if name_is_lora(k)}
def cast_base_to_precision(model, precision):
"""Cast frozen base weights to lower precision, keeping LoRA params in fp32.
Args:
model: Model with LoRA parametrizations applied.
precision: One of "bf16", "bfloat16", "fp16", "float16".
"""
import torch
dtype_map = {"bf16": torch.bfloat16, "bfloat16": torch.bfloat16,
"fp16": torch.float16, "float16": torch.float16}
target_dtype = dtype_map.get(precision)
if target_dtype is None:
print(f"Unknown base_precision={precision!r}, keeping fp32")
return
vprint(f"Casting frozen base weights to {target_dtype}")
model.to(target_dtype)
# Restore LoRA params to fp32 — optimizer needs full precision
n_restored = 0
for p in get_lora_params(model):
p.data = p.data.to(torch.float32)
n_restored += 1
vprint(f" Restored {n_restored} LoRA params to fp32")
# ------------------- helper function for loading LoRa at inference -------------------
def infer_global_rank(lora_sd: dict) -> int:
candidates = []
for k, v in lora_sd.items():
if k.endswith(".lora_A"):
candidates.append(v.shape[0])
elif k.endswith(".lora_B"):
candidates.append(v.shape[1])
elif k.endswith(".M_xs"):
candidates.append(v.shape[0])
if not candidates:
raise ValueError("No LoRA/LoRA-XS tensors found to infer rank")
r = candidates[0]
# sanity check they all match (most checkpoints use one global r)
if any(c != r for c in candidates):
print(f"Multiple ranks detected in ckpt: {sorted(set(candidates))}. "
"You’ll need per-layer ranks.")
return r
def detect_dora_variant(state_dict):
"""Detect DoRA variant from a state dict.
Returns "dora-cols" (dim=0, per-input-feature) or "dora-rows" (dim=1,
per-output-neuron) based on the shape of the first .magnitude tensor
found. Returns None if no magnitude tensors exist or axis is ambiguous.
"""
for key, val in state_dict.items():
if not key.endswith(".magnitude"):
continue
if val.dim() == 2:
if val.shape[0] == 1:
return "dora-cols" # old format: (1, fan_in)
elif val.shape[1] == 1:
return "dora-rows" # (fan_out, 1)
# 1D magnitude — can't determine axis without model context
return None
return None
def prepare_dora_state_dict(state_dict):
"""Squeeze 2D DoRA magnitude tensors to 1D for loading. Modifies in-place."""
for key in list(state_dict.keys()):
if key.endswith(".magnitude") and state_dict[key].dim() == 2:
state_dict[key] = state_dict[key].squeeze()
def resolve_adapter_type(adapter_type, state_dict=None):
"""Resolve legacy "dora" adapter_type to the correct variant.
If adapter_type is "dora" and a checkpoint state_dict is provided,
attempts to detect dora-rows vs dora-cols from magnitude tensor shapes.
Falls back to "dora-rows" (paper-correct default).
All other adapter types are returned unchanged.
"""
if adapter_type != "dora":
return adapter_type
if state_dict is not None:
detected = detect_dora_variant(state_dict)
if detected:
return detected
return "dora-rows"
# ------------------- helper functions for safetensors LoRA checkpoints -------------------
def save_lora_safetensors(state_dict, lora_config, path):
"""Save a LoRA checkpoint in safetensors format with config as metadata.
The lora_config dict is JSON-serialized and stored under the metadata key
``"lora_config"``, which safetensors stores in the file header as a string.
Args:
state_dict: Dict of LoRA tensors (from get_lora_state_dict).
lora_config: Dict with keys like rank, alpha, adapter_type, include, exclude.
path: Output file path (should end in .safetensors).
"""
metadata = {"lora_config": json.dumps(lora_config)}
fp16_dict = {k: v.half() if v.is_floating_point() else v for k, v in state_dict.items()}
_st_save_file(fp16_dict, str(path), metadata=metadata)
def load_lora_checkpoint(path):
"""Load a LoRA checkpoint from either .safetensors or .ckpt format.
Returns:
Tuple of (state_dict, lora_config) where lora_config may be an empty
dict if the checkpoint has no embedded config.
"""
path = str(path)
if path.endswith(".safetensors"):
with safe_open(path, framework="pt", device="cpu") as f:
state_dict = {key: f.get_tensor(key) for key in f.keys()}
metadata = f.metadata()
lora_config = {}
if metadata and "lora_config" in metadata:
lora_config = json.loads(metadata["lora_config"])
return state_dict, lora_config
else:
ckpt = torch.load(path, map_location="cpu", weights_only=False)
state_dict = ckpt["state_dict"]
lora_config = ckpt.get("lora_config", {})
return state_dict, lora_config
def convert_lora_ckpt_to_safetensors(ckpt_path, output_path=None):
"""Convert a .ckpt LoRA checkpoint to .safetensors format.
Args:
ckpt_path: Path to the input .ckpt file.
output_path: Path for the output .safetensors file.
If None, replaces .ckpt extension with .safetensors.
Returns:
The output path.
"""
ckpt_path = str(ckpt_path)
if output_path is None:
if ckpt_path.endswith(".ckpt"):
output_path = ckpt_path[:-5] + ".safetensors"
else:
output_path = ckpt_path + ".safetensors"
state_dict, lora_config = load_lora_checkpoint(ckpt_path)
save_lora_safetensors(state_dict, lora_config, output_path)
return output_path
# ------------------- helper functions for multi-LoRA stacking -------------------
def get_lora_count(model):
"""Return the number of stacked LoRAs (distinct lora_index values)."""
indices = set()
for _, p in get_lora_layers(model):
indices.add(p.lora_index)
return len(indices)
def remap_lora_state_dict(state_dict, target_index):
"""Remap LoRA state dict keys from parametrization index 0 to target_index.
Checkpoints always save keys with index 0 (e.g. *.parametrizations.weight.0.lora_A).
When loading the Nth stacked LoRA, we need to remap to index N.
"""
if target_index == 0:
return state_dict
remapped = {}
for k, v in state_dict.items():
new_key = k.replace(
".parametrizations.weight.0.",
".parametrizations.weight.{}.".format(target_index)
)
remapped[new_key] = v
return remapped
# ------------------- helper function for inferencing with multiple lora -------------------
def _get_adapter_param_names(lora_layer):
"""Return the list of trainable parameter names for this adapter type."""
if lora_layer.adapter_type == "lora":
return ["lora_A", "lora_B"]
elif lora_layer.adapter_type in ("dora", "dora-rows", "dora-cols"):
return ["lora_A", "lora_B", "magnitude"]
elif lora_layer.adapter_type == "bora":
return ["lora_A", "lora_B", "magnitude_r", "magnitude_c"]
elif lora_layer.adapter_type == "lora-xs":
return ["M_xs"]
elif lora_layer.adapter_type in ("dora-rows-xs", "dora-cols-xs"):
return ["M_xs", "magnitude"]
elif lora_layer.adapter_type == "bora-xs":
return ["M_xs", "magnitude_r", "magnitude_c"]
return []
def _prepare_for_multiple_lora(lora_layer):
lora_layer._multi_lora_slots = []
def _append_lora(lora_layer):
slot = {}
for name in _get_adapter_param_names(lora_layer):
slot[name] = nn.Parameter(getattr(lora_layer, name).clone())
lora_layer._multi_lora_slots.append(slot)
def load_multiple_lora(model, lora_state_dicts):
model.apply(apply_to_lora(_prepare_for_multiple_lora))
for state_dict in lora_state_dicts:
_ = model.load_state_dict(state_dict, strict=False)
model.apply(apply_to_lora(_append_lora))
return model
def _select_lora(lora_layer, index):
slot = lora_layer._multi_lora_slots[index]
for name, param in slot.items():
setattr(lora_layer, name, param)
def select_lora(model, index):
model.apply(apply_to_lora(lambda x: _select_lora(x, index)))
return model
# ------------------- helper function for merging multiple LoRAs -------------------
def merge_loras_into_base_model(model, lora_configurations):
"""
Merges multiple LoRAs with specified application weights directly into the base model's weights.
Works with all adapter types (LoRA, DoRA, LoRA-XS) by using the parametrization's own forward.
After merging, the LoRA parametrization on each layer is disabled.
Args:
model: The PyTorch model with LoRAParametrization already applied (via add_lora).
lora_configurations: A list of dicts, each specifying a LoRA to merge:
[
{
'name': 'style1',
'state_dict': loaded_torch_state_dict,
'application_weight': 0.7
},
...
]
"""
import torch
if not lora_configurations:
return
# Collect all parametrized layers with their LoRA instances
parametrized_layers = []
for name, module in model.named_modules():
if not hasattr(module, "parametrizations"):
continue
for attr_name in list(module.parametrizations.keys()):
plist = module.parametrizations[attr_name]
for p in plist:
if isinstance(p, LoRAParametrization):
parametrized_layers.append((name, module, attr_name, p, plist.original))
break
# Initialize per-layer delta accumulators
deltas = {id(orig): torch.zeros_like(orig.data) for _, _, _, _, orig in parametrized_layers}
for lora_cfg in lora_configurations:
lora_sd = lora_cfg['state_dict']
app_weight = lora_cfg['application_weight']
# Load this LoRA's adapter weights into the model's parametrizations
model.load_state_dict(lora_sd, strict=False)
# Compute each layer's delta using the parametrization's own forward
with torch.no_grad():
for name, module, attr_name, lora_p, original in parametrized_layers:
merged = lora_p(original)
deltas[id(original)] += app_weight * (merged - original)
# Apply accumulated deltas to original weights and disable LoRA
with torch.no_grad():
for name, module, attr_name, lora_p, original in parametrized_layers:
original.data.add_(deltas[id(original)])
lora_p.disable_lora()
# ------------------- helper function for tying and untieing weights -------------------
def tie_weights(linear: nn.Linear, embedding: nn.Embedding):
"""tie the weights of the linear layer and the embedding layer both with the same lora"""
lora_p = embedding.parametrizations.weight[0]
if lora_p.adapter_type != "lora":
raise NotImplementedError(f"tie_weights only supports standard LoRA, got adapter_type='{lora_p.adapter_type}'")
# this line below is optional if the original is already tied
embedding.parametrizations.weight.original = linear.parametrizations.weight.original
embedding.parametrizations.weight[0].lora_A = linear.parametrizations.weight[0].lora_B
embedding.parametrizations.weight[0].lora_B = linear.parametrizations.weight[0].lora_A
def untie_weights(linear: nn.Linear, embedding: nn.Embedding):
"""untie the weights of the linear layer and the embedding layer"""
lora_p = embedding.parametrizations.weight[0]
if lora_p.adapter_type != "lora":
raise NotImplementedError(f"untie_weights only supports standard LoRA, got adapter_type='{lora_p.adapter_type}'")
embedding.parametrizations.weight.original = nn.Parameter(embedding.weight.original.clone())
embedding.parametrizations.weight[0].lora_A = nn.Parameter(embedding.parametrizations.weight[0].lora_A.clone())
embedding.parametrizations.weight[0].lora_B = nn.Parameter(embedding.parametrizations.weight[0].lora_B.clone())
@@ -0,0 +1,84 @@
import torch
import torch.nn as nn
from einops import rearrange
from torchaudio.transforms import Resample
from .blocks import ResidualUnit, WNConv1d
class AutoencoderPretransform(nn.Module):
def __init__(self, model, scale=1.0, iterate_batch=False, chunked=False):
super().__init__()
self.model = model
self.model.requires_grad_(False).eval()
self.scale = scale
self.downsampling_ratio = model.downsampling_ratio
self.io_channels = model.io_channels
self.enable_grad = False
self.iterate_batch = iterate_batch
self.chunked = chunked
def encode(self, x, **kwargs):
return self.model.encode_audio(x, chunked=self.chunked, iterate_batch=self.iterate_batch, **kwargs) / self.scale
def decode(self, z, chunked=None, **kwargs):
chunked = self.chunked if chunked is None else chunked
return self.model.decode_audio(z * self.scale, chunked=chunked, iterate_batch=self.iterate_batch, **kwargs)
def fold_channels_into_batch(x):
x = rearrange(x, 'b c ... -> (b c) ...')
return x
def unfold_channels_from_batch(x, channels):
if channels == 1:
return x.unsqueeze(1)
x = rearrange(x, '(b c) ... -> b c ...', c = channels)
return x
class PatchedPretransform(nn.Module):
def __init__(self, channels, patch_size, oversampling = 1, postfilter_channels = 0, **kwargs):
super().__init__()
self.channels = channels
self.patch_size = patch_size
self.oversampling = oversampling
self.downsampling_ratio = patch_size
self.io_channels = channels
self.encoded_channels = channels * patch_size
self.enable_grad = False
if self.oversampling > 1:
self.input_upsampler = Resample(1, self.oversampling)
self.output_downsampler = Resample(self.oversampling, 1)
if postfilter_channels > 0:
self.postfilter = nn.Sequential(
WNConv1d(in_channels=channels, out_channels=postfilter_channels, kernel_size=7, padding=3, bias=True),
ResidualUnit(in_channels=postfilter_channels, out_channels=postfilter_channels,
dilation=1, use_snake=True, bias = True),
ResidualUnit(in_channels=postfilter_channels, out_channels=postfilter_channels,
dilation=3, use_snake=True, bias = True),
ResidualUnit(in_channels=postfilter_channels, out_channels=postfilter_channels,
dilation=9, use_snake=True, bias = True),
WNConv1d(in_channels=postfilter_channels, out_channels=channels, kernel_size=7, padding=3, bias=False))
def _pad(self, x):
seq_len = x.shape[-1]
pad_len = (self.patch_size - (seq_len % self.patch_size)) % self.patch_size
if pad_len > 0:
x = torch.cat([x, torch.zeros_like(x[:, :, :pad_len])], dim=-1)
return x
def encode(self, x):
if self.oversampling > 1:
x = self.input_upsampler(x)
x = self._pad(x)
x = rearrange(x, "b c (l h) -> b (c h) l", h=self.patch_size)
return x
def decode(self, x):
x = rearrange(x, "b (c h) l -> b c (l h)", h=self.patch_size)
if hasattr(self, 'postfilter'):
x = self.postfilter(x)
if self.oversampling > 1:
x = self.output_downsampler(x)
return x
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,19 @@
import torch
import os
torch._dynamo.config.cache_size_limit = max(64, torch._dynamo.config.cache_size_limit)
torch._dynamo.config.suppress_errors = True
# Get torch.compile flag from environment variable ENABLE_TORCH_COMPILE
enable_torch_compile = os.environ.get("ENABLE_TORCH_COMPILE", "0") == "1"
def compile(function, *args, **kwargs):
if enable_torch_compile:
try:
return torch.compile(function, *args, **kwargs)
except RuntimeError:
return function
return function