Download scripts/v3mini_padding_safe.py from owensong/Inflect-Mini-v3-Preview: direct link, hf CLI and curl.
- Browser
- Download file 12.5 kB
-
https://huggingface.co/owensong/Inflect-Mini-v3-Preview/resolve/main/scripts/v3mini_padding_safe.py
- Command line
-
hf download hf://owensong/Inflect-Mini-v3-Preview/scripts/v3mini_padding_safe.py
-
curl -L -o v3mini_padding_safe.py https://huggingface.co/owensong/Inflect-Mini-v3-Preview/resolve/main/scripts/v3mini_padding_safe.py
12.5 kB
| """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]) | |
| 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) | |
| 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) | |
| 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 | |
| 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 | |