File size: 12,529 Bytes
5f18a20 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | """Explicit padding-safe native Matcha, with unchanged learned state layout.
``create(config)`` accepts the unchanged capacity-192 parent configuration.
Callers must record ARCHITECTURE_ID separately in branch/checkpoint metadata:
identical state keys do not identify the changed forward contract. In particular,
load the CONTROL129000 no-adapter parent strictly, not a rich-text adapter state.
The returned MatchaTTS retains its native ``forward`` and ``synthesise`` methods.
No trainer, frontend, optimizer, dropout, vocoder, or global forward hooks change.
Only PyTorch is required to import the numerical helpers. ``native_types`` and
``create`` additionally require the installed native Matcha dependency stack.
"""
from functools import lru_cache, partial
from pathlib import Path
import sys
import torch
from torch import nn
ARCHITECTURE_ID = "inflect-v3mini-matcha-padding-safe-v1"
ACOUSTIC_PARAMETERS = 10_780_425
def boolean_sequence_mask(mask, x, *, prefix=False):
"""Validate Bx1xT binary validity; True means an allowed attention key.
Additive masks (including 0/-inf) are deliberately rejected, not cast to bool.
Every example must contain at least one valid frame.
"""
if x.ndim != 3 or x.shape[0] == 0 or x.shape[-1] == 0:
raise ValueError("Expected a nonempty BxCxT tensor")
if mask.shape != (x.shape[0], 1, x.shape[-1]) or mask.device != x.device:
raise ValueError("Mask must have shape Bx1xT on the input device")
if not torch.all((mask == 0) | (mask == 1)):
raise ValueError("Mask must be finite binary validity, not additive attention bias")
valid = mask.bool()
if not valid.any(dim=-1).all():
raise ValueError("Every example must contain a nonempty valid sequence")
if prefix and torch.any(valid[..., 1:] & ~valid[..., :-1]):
raise ValueError("Decoder masks must be right-padded contiguous prefixes")
return valid
def _zero_padding(x, valid):
# Multiplication by zero does not remove NaN/Inf padding.
return x.masked_fill(~valid, 0)
def _group_norm(x, valid, norm):
b, c, length = x.shape
if c != norm.num_channels:
raise ValueError("GroupNorm channel count differs from input")
# Match native accumulation precision under autocast without lowering FP64.
work = _zero_padding(x, valid)
if work.dtype in (torch.float16, torch.bfloat16):
work = work.float()
grouped = work.reshape(b, norm.num_groups, c // norm.num_groups, length)
weights = valid.unsqueeze(1)
count = weights.sum((2, 3), keepdim=True).to(work.dtype) * (c // norm.num_groups)
mean = grouped.sum((2, 3), keepdim=True) / count
centered = (grouped - mean).masked_fill(~weights, 0)
variance = centered.square().sum((2, 3), keepdim=True) / count
output = (centered * torch.rsqrt(variance + norm.eps)).reshape(b, c, length)
if norm.affine:
output = output * norm.weight.to(work.dtype)[None, :, None]
output = output + norm.bias.to(work.dtype)[None, :, None]
return _zero_padding(output.to(x.dtype), valid)
def valid_frame_group_norm(x, mask, norm):
"""Biased GroupNorm statistics over valid time and channels within each group.
Uses the supplied native nn.GroupNorm's eps and learned affine tensors;
registers no parameters/buffers. Invalid outputs and input gradients are zero.
"""
return _group_norm(x, boolean_sequence_mask(mask, x), norm)
def masked_flow_loss(prediction, target, mask):
"""CFM squared error divided by valid frames times mel channels, not padding."""
if prediction.shape != target.shape:
raise ValueError("Flow prediction and target shapes must match")
valid = boolean_sequence_mask(mask, prediction)
prediction, target = _zero_padding(prediction, valid), _zero_padding(target, valid)
if prediction.dtype in (torch.float16, torch.bfloat16):
prediction, target = prediction.float(), target.float()
error = prediction - target
return error.square().sum() / (valid.sum() * target.shape[1])
@lru_cache(maxsize=1)
def native_decoder_types():
"""Return (PaddingSafeBlock1D, PaddingSafeDecoder) lazily.
Constructors run the vendor constructors first. Replacement initialization is
RNG-neutral and strictly copies state, retaining registration order and names.
No vendor classes, existing models, or attention processors are patched.
"""
vendor = str(Path(__file__).resolve().parents[1] / "external/Matcha-TTS-primary")
if vendor not in sys.path:
sys.path.insert(0, vendor)
from matcha.models.components.decoder import Block1D, Decoder
class PaddingSafeBlock1D(Block1D):
def forward(self, x, mask):
return self._forward_validated(x, boolean_sequence_mask(mask, x))
def _forward_validated(self, x, valid):
h = self.block[0](_zero_padding(x, valid))
return _zero_padding(self.block[2](_group_norm(h, valid, self.block[1])), valid)
class PaddingSafeDecoder(Decoder):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
from matcha.models.components.transformer import BasicTransformerBlock
for stage in (self.down_blocks, self.mid_blocks, self.up_blocks):
for parts in stage:
if any(not isinstance(block, BasicTransformerBlock) for block in parts[1]):
raise ValueError("Padding-safe variant supports native transformer blocks only")
for module in list(self.modules()):
for name, child in list(module.named_children()):
if isinstance(child, Block1D):
conv, norm = child.block[:2]
with torch.random.fork_rng(devices=[]):
replacement = PaddingSafeBlock1D(
conv.in_channels, conv.out_channels, groups=norm.num_groups)
replacement.load_state_dict(child.state_dict(), strict=True)
setattr(module, name, replacement)
@staticmethod
def _resnet(resnet, x, valid, t):
# Same native ResnetBlock1D operations, but the decoder has already
# validated this mask. Avoid per-Block1D device-to-host reductions.
x = _zero_padding(x, valid)
h = resnet.block1._forward_validated(x, valid)
h += resnet.mlp(t).unsqueeze(-1)
h = resnet.block2._forward_validated(h, valid)
return h + resnet.res_conv(x)
@staticmethod
def _attention(x, blocks, valid, t):
x = _zero_padding(x, valid).transpose(1, 2)
# Explicit singleton query axis: Diffusers expands heads and SDPA
# broadcasts queries. A BxT mask alone can misbroadcast for B > 1.
for block in blocks:
x = block(hidden_states=x, attention_mask=valid, timestep=t)
return _zero_padding(x.transpose(1, 2), valid)
def forward(self, x, mask, mu, t, spks=None, cond=None):
valid = boolean_sequence_mask(mask, x, prefix=True)
if mu.shape != x.shape:
raise ValueError("Flow input and text mean shapes must match")
if cond is not None:
raise ValueError("Native padding-safe decoder has no adapter conditioning")
x, mu = _zero_padding(x, valid), _zero_padding(mu, valid)
if not torch.isfinite(x).all() or not torch.isfinite(mu).all():
raise ValueError("Valid flow input and text mean must be finite")
t = self.time_mlp(self.time_embeddings(t))
x = torch.cat((x, mu), dim=1)
if spks is not None:
x = torch.cat((x, spks.unsqueeze(-1).expand(-1, -1, x.shape[-1])), dim=1)
hiddens, masks = [], []
current = valid
for index, (resnet, blocks, downsample) in enumerate(self.down_blocks):
x = self._resnet(resnet, x, current, t)
x = self._attention(x, blocks, current, t)
hiddens.append(x)
masks.append(current)
x = downsample(x)
# Native's final down block is stride one, not a downsample.
if index < len(self.down_blocks) - 1:
current = current[..., ::2]
x = _zero_padding(x, current)
for resnet, blocks in self.mid_blocks:
x = self._resnet(resnet, x, current, t)
x = self._attention(x, blocks, current, t)
for resnet, blocks, upsample in self.up_blocks:
current, skip = masks.pop(), hiddens.pop()
# Stride-two transposed convolutions produce one extra right
# frame for odd skip lengths. Crop before the next convolution.
x = x[..., :skip.shape[-1]]
x = self._resnet(resnet, torch.cat((x, skip), dim=1), current, t)
x = self._attention(x, blocks, current, t)
x = upsample(x)
x = self.final_block._forward_validated(x, valid)
return _zero_padding(self.final_proj(x), valid)
return PaddingSafeBlock1D, PaddingSafeDecoder
@lru_cache(maxsize=1)
def native_types():
"""Return decoder types plus PaddingSafeCFM with native training dependencies."""
PaddingSafeBlock1D, PaddingSafeDecoder = native_decoder_types()
from matcha.models.components.flow_matching import CFM
class PaddingSafeCFM(CFM):
def __init__(self, in_channels, out_channel, cfm_params, decoder_params,
n_spks=1, spk_emb_dim=64):
super().__init__(in_channels, out_channel, cfm_params, decoder_params, n_spks, spk_emb_dim)
with torch.random.fork_rng(devices=[]):
estimator = PaddingSafeDecoder(
in_channels=in_channels + (spk_emb_dim if n_spks > 1 else 0),
out_channels=out_channel, **decoder_params)
estimator.load_state_dict(self.estimator.state_dict(), strict=True)
self.estimator = estimator
def compute_loss(self, x1, mask, mu, spks=None, cond=None):
valid = boolean_sequence_mask(mask, x1, prefix=True)
if mu.shape != x1.shape:
raise ValueError("Flow target and text mean shapes must match")
x1 = _zero_padding(x1, valid)
if not torch.isfinite(x1).all():
raise ValueError("Valid flow target must be finite")
t = torch.rand([mu.shape[0], 1, 1], device=mu.device, dtype=mu.dtype)
z = torch.randn_like(x1)
y = (1 - (1 - self.sigma_min) * t) * z + t * x1
u = x1 - (1 - self.sigma_min) * z
prediction = self.estimator(y, valid, mu, t.squeeze(), spks, cond)
return masked_flow_loss(prediction, u, valid), y
return PaddingSafeBlock1D, PaddingSafeDecoder, PaddingSafeCFM
def create(config):
"""Build FP32 native-compatible acoustic model; strict state_dict loading only.
Config is the unchanged capacity-192/full-context config, including its parent
architecture marker. Persist ARCHITECTURE_ID outside it to distinguish this
experimental forward contract. This function does not load a checkpoint or
create/restore an optimizer; native optimizer configuration is preserved.
"""
from scripts import train_v3mini_unet_capacity_192 as native
values = native.native_config(config)
_, _, PaddingSafeCFM = native_types()
from omegaconf import OmegaConf
from matcha.models.matcha_tts import MatchaTTS
for key in ("encoder", "decoder", "cfm"):
values[key] = OmegaConf.create(values[key])
model = MatchaTTS(**values, optimizer=partial(torch.optim.Adam, lr=1e-4))
original = model.decoder
with torch.random.fork_rng(devices=[]):
replacement = PaddingSafeCFM(
2 * values["encoder"].encoder_params.n_feats,
values["encoder"].encoder_params.n_feats,
values["cfm"], values["decoder"], values["n_spks"], values["spk_emb_dim"])
replacement.load_state_dict(original.state_dict(), strict=True)
model.decoder = replacement
model.architecture_id = ARCHITECTURE_ID
model.float()
if sum(p.numel() for p in model.parameters()) != ACOUSTIC_PARAMETERS:
raise ValueError("Padding-safe acoustic parameter count must be exactly 10,780,425")
native.parameter_budget(model)
return model
|