mlr2000 commited on
Commit
f6d3f28
Β·
verified Β·
1 Parent(s): 2591eab

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ tags:
4
+ - audio
5
+ - text-to-speech
6
+ - vocoder
7
+ - hifi-gan
8
+ - bigvgan
9
+ - neural-vocoder
10
+ - speaker-conditioning
11
+ - audio-watermarking
12
+ pipeline_tag: text-to-speech
13
+ license: cc-by-4.0
14
+ ---
15
+
16
+ # VocBulwark HiFi-GAN β€” watermarking neural vocoder (inference export)
17
+
18
+ Speaker-conditioned **BigVGAN / HiFi-GAN** neural vocoder. It turns an input mel-spectrogram (*what* is said) into a 24 kHz waveform, conditioned on a **precomputed 768-d speaker embedding** (*whose* voice). Every clip it generates carries a **fixed 50-bit provenance watermark** that identifies this specific model instance, see [Watermark](#watermark).
19
+
20
+ This is the **lean, inference-only** vocoder: the frozen perceptual-loss base models (Whisper / WavLM / Wav2Vec2), the training discriminators, **and the speaker encoder** have all been stripped, you pass the speaker embedding in. Use the companion **speaker-encoder** repo to turn a reference clip into that embedding. The modeling code is bundled, so it loads with `trust_remote_code=True` **without** the training repo.
21
+
22
+ ## Model summary
23
+
24
+ | | |
25
+ |---|---|
26
+ | Architecture | `HiFiGANArchitecture` (BigVGAN generator, snakebeta activation) |
27
+ | Inputs | log-mel spectrogram (**96 mel channels**) + speaker embedding (**768-d**) |
28
+ | Output | mono waveform, **24 kHz** |
29
+ | Speaker conditioning | precomputed embedding (from the companion speaker encoder) |
30
+ | Generator | initial channels 1536, upsample rates [4, 4, 2, 2, 2, 2] |
31
+ | Watermark | 50-bit fixed VocBulwark signature, always embedded |
32
+ | Framework | πŸ€— Transformers, PyTorch, safetensors |
33
+
34
+
35
+ ## Companion Models
36
+
37
+ This model is part of a set of 6 repositories:
38
+
39
+ | Repo | Role |
40
+ |------|------|
41
+ | `mlr2000/vocoder-large` | Large vocoder (this repo) |
42
+ | `mlr2000/vocoder-large-watermark-detector` | Watermark detector for the large model |
43
+ | `mlr2000/vocoder-large-speaker-encoder` | Speaker encoder for the large model |
44
+ | `mlr2000/vocoder-small` | Small vocoder |
45
+ | `mlr2000/vocoder-small-watermark-detector` | Watermark detector for the small model |
46
+ | `mlr2000/vocoder-small-speaker-encoder` | Speaker encoder for the small model |
47
+
48
+
49
+ ## Usage
50
+
51
+ ```python
52
+ import torch
53
+ from transformers import AutoModel
54
+
55
+ model = AutoModel.from_pretrained("mlr2000/vocoder-large", trust_remote_code=True).eval()
56
+
57
+ mel = torch.randn(1, model.config.hifigan_in_channels, 200) # [B, mel, T]
58
+ emb = torch.randn(1, model.config.speaker_embedding_size) # [B, 768] from the speaker encoder
59
+ with torch.no_grad():
60
+ audio = model(mel_spectrogram=mel, speaker_embedding=emb).audio
61
+ # audio: [B, 1, samples] @ model.config.target_sample_rate
62
+ ```
63
+
64
+ See **`example_roundtrip.ipynb`** in this repo for the full pipeline (reference
65
+ clip β†’ speaker encoder β†’ embedding β†’ vocode β†’ verify watermark).
66
+
67
+ ## Training
68
+
69
+ | | |
70
+ |---|---|
71
+ | Training data | Multilingual LibriSpeech (8 languages, ~22,200h) and Common Voice (14 languages, ~3,000h) |
72
+ | Training steps | 1,000,000 |
73
+ | Hardware | 2 Γ— NVIDIA H200 GPUs |
74
+ | Training objective | Discriminator-free: mel spectrogram + WavLM + wav2vec 2.0 + Whisper encoder losses |
75
+ | Effective batch size | 32 |
76
+ | Learning rate | 1e-4 |
77
+
78
+
79
+ ## Watermark
80
+
81
+ Every clip this model generates carries a **fixed 50-bit provenance watermark** (`config.fixed_watermark`) that identifies this specific model instance. It is embedded automatically inside `forward` and **cannot be disabled or changed** through this interface β€” there is deliberately no watermark argument to override.
82
+
83
+ To verify whether a given audio clip was generated by this model, use the companion **detector** repo (`mlr2000/vocoder-large-watermark-detector`), which extracts the embedded bits and compares them to the known fixed code.
84
+
85
+
86
+ ## Notes
87
+
88
+ - Inputs: log-mel spectrogram (`config.hifigan_in_channels` channels) and a
89
+ `[B, config.speaker_embedding_size]` speaker embedding.
90
+ - Output: mono waveform at `config.target_sample_rate`.
91
+ - Use the embedding from the speaker encoder this vocoder was **trained with**, a
92
+ mismatched encoder will not condition it correctly.
93
+ - Not intended for voice cloning of real individuals without consent, or any
94
+ deceptive / impersonation use.
95
+
96
+ ## Citation
97
+
98
+ If you use this model, please cite:
99
+
100
+ ```bibtex
101
+ @misc{muletta2026,
102
+ title = {Training a Discriminator-Free Foundation Vocoder
103
+ with Integrated Audio Watermarking},
104
+ author = {Muletta, Romolo and Deriu, Jan},
105
+ year = {2026},
106
+ note = {VT2 Project Report, ZHAW School of Engineering}
107
+ }
108
+ ```
109
+
110
+ ## License
111
+
112
+ `cc-by-4.0`. Trained on MLS (CC-BY-4.0) and Common Voice (CC0); builds on
113
+ BigVGAN (MIT) and wav2vec 2.0 (Apache-2.0). Please retain attribution when
114
+ redistributing or building on this model.
__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .configuration_hifigan import HiFiGANConfig
2
+ from .modeling_hifigan import HiFiGANArchitecture, HiFiGANOutput
3
+
4
+ __all__ = ["HiFiGANConfig", "HiFiGANArchitecture", "HiFiGANOutput"]
alias_free_act.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
2
+ # LICENSE is in incl_licenses directory.
3
+
4
+ import torch.nn as nn
5
+ from .alias_free_resample import UpSample1d, DownSample1d
6
+
7
+
8
+ class Activation1d(nn.Module):
9
+ def __init__(
10
+ self,
11
+ activation,
12
+ up_ratio: int = 2,
13
+ down_ratio: int = 2,
14
+ up_kernel_size: int = 12,
15
+ down_kernel_size: int = 12,
16
+ ):
17
+ super().__init__()
18
+ self.up_ratio = up_ratio
19
+ self.down_ratio = down_ratio
20
+ self.act = activation
21
+ self.upsample = UpSample1d(up_ratio, up_kernel_size)
22
+ self.downsample = DownSample1d(down_ratio, down_kernel_size)
23
+
24
+ # x: [B,C,T]
25
+ def forward(self, x):
26
+ x = self.upsample(x)
27
+ x = self.act(x)
28
+ x = self.downsample(x)
29
+
30
+ return x
alias_free_filter.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
2
+ # LICENSE is in incl_licenses directory.
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ import math
8
+
9
+ if "sinc" in dir(torch):
10
+ sinc = torch.sinc
11
+ else:
12
+ # This code is adopted from adefossez's julius.core.sinc under the MIT License
13
+ # https://adefossez.github.io/julius/julius/core.html
14
+ # LICENSE is in incl_licenses directory.
15
+ def sinc(x: torch.Tensor):
16
+ """
17
+ Implementation of sinc, i.e. sin(pi * x) / (pi * x)
18
+ __Warning__: Different to julius.sinc, the input is multiplied by `pi`!
19
+ """
20
+ return torch.where(
21
+ x == 0,
22
+ torch.tensor(1.0, device=x.device, dtype=x.dtype),
23
+ torch.sin(math.pi * x) / math.pi / x,
24
+ )
25
+
26
+
27
+ # This code is adopted from adefossez's julius.lowpass.LowPassFilters under the MIT License
28
+ # https://adefossez.github.io/julius/julius/lowpass.html
29
+ # LICENSE is in incl_licenses directory.
30
+ def kaiser_sinc_filter1d(
31
+ cutoff, half_width, kernel_size
32
+ ): # return filter [1,1,kernel_size]
33
+ even = kernel_size % 2 == 0
34
+ half_size = kernel_size // 2
35
+
36
+ # For kaiser window
37
+ delta_f = 4 * half_width
38
+ A = 2.285 * (half_size - 1) * math.pi * delta_f + 7.95
39
+ if A > 50.0:
40
+ beta = 0.1102 * (A - 8.7)
41
+ elif A >= 21.0:
42
+ beta = 0.5842 * (A - 21) ** 0.4 + 0.07886 * (A - 21.0)
43
+ else:
44
+ beta = 0.0
45
+ window = torch.kaiser_window(kernel_size, beta=beta, periodic=False)
46
+
47
+ # ratio = 0.5/cutoff -> 2 * cutoff = 1 / ratio
48
+ if even:
49
+ time = torch.arange(-half_size, half_size) + 0.5
50
+ else:
51
+ time = torch.arange(kernel_size) - half_size
52
+ if cutoff == 0:
53
+ filter_ = torch.zeros_like(time)
54
+ else:
55
+ filter_ = 2 * cutoff * window * sinc(2 * cutoff * time)
56
+ """
57
+ Normalize filter to have sum = 1, otherwise we will have a small leakage of the constant component in the input signal.
58
+ """
59
+ filter_ /= filter_.sum()
60
+ filter = filter_.view(1, 1, kernel_size)
61
+
62
+ return filter
63
+
64
+
65
+ class LowPassFilter1d(nn.Module):
66
+ def __init__(
67
+ self,
68
+ cutoff=0.5,
69
+ half_width=0.6,
70
+ stride: int = 1,
71
+ padding: bool = True,
72
+ padding_mode: str = "replicate",
73
+ kernel_size: int = 12,
74
+ ):
75
+ """
76
+ kernel_size should be even number for stylegan3 setup, in this implementation, odd number is also possible.
77
+ """
78
+ super().__init__()
79
+ if cutoff < -0.0:
80
+ raise ValueError("Minimum cutoff must be larger than zero.")
81
+ if cutoff > 0.5:
82
+ raise ValueError("A cutoff above 0.5 does not make sense.")
83
+ self.kernel_size = kernel_size
84
+ self.even = kernel_size % 2 == 0
85
+ self.pad_left = kernel_size // 2 - int(self.even)
86
+ self.pad_right = kernel_size // 2
87
+ self.stride = stride
88
+ self.padding = padding
89
+ self.padding_mode = padding_mode
90
+ filter = kaiser_sinc_filter1d(cutoff, half_width, kernel_size)
91
+ self.register_buffer("filter", filter)
92
+
93
+ # Input [B, C, T]
94
+ def forward(self, x):
95
+ _, C, _ = x.shape
96
+
97
+ if self.padding:
98
+ x = F.pad(x, (self.pad_left, self.pad_right), mode=self.padding_mode)
99
+ out = F.conv1d(x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C)
100
+
101
+ return out
alias_free_resample.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
2
+ # LICENSE is in incl_licenses directory.
3
+
4
+ import torch.nn as nn
5
+ from torch.nn import functional as F
6
+ from .alias_free_filter import LowPassFilter1d
7
+ from .alias_free_filter import kaiser_sinc_filter1d
8
+
9
+
10
+ class UpSample1d(nn.Module):
11
+ def __init__(self, ratio=2, kernel_size=None):
12
+ super().__init__()
13
+ self.ratio = ratio
14
+ self.kernel_size = (
15
+ int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size
16
+ )
17
+ self.stride = ratio
18
+ self.pad = self.kernel_size // ratio - 1
19
+ self.pad_left = self.pad * self.stride + (self.kernel_size - self.stride) // 2
20
+ self.pad_right = (
21
+ self.pad * self.stride + (self.kernel_size - self.stride + 1) // 2
22
+ )
23
+ filter = kaiser_sinc_filter1d(
24
+ cutoff=0.5 / ratio, half_width=0.6 / ratio, kernel_size=self.kernel_size
25
+ )
26
+ self.register_buffer("filter", filter)
27
+
28
+ # x: [B, C, T]
29
+ def forward(self, x):
30
+ _, C, _ = x.shape
31
+
32
+ x = F.pad(x, (self.pad, self.pad), mode="replicate")
33
+ x = self.ratio * F.conv_transpose1d(
34
+ x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C
35
+ )
36
+ x = x[..., self.pad_left : -self.pad_right]
37
+
38
+ return x
39
+
40
+
41
+ class DownSample1d(nn.Module):
42
+ def __init__(self, ratio=2, kernel_size=None):
43
+ super().__init__()
44
+ self.ratio = ratio
45
+ self.kernel_size = (
46
+ int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size
47
+ )
48
+ self.lowpass = LowPassFilter1d(
49
+ cutoff=0.5 / ratio,
50
+ half_width=0.6 / ratio,
51
+ stride=ratio,
52
+ kernel_size=self.kernel_size,
53
+ )
54
+
55
+ def forward(self, x):
56
+ xx = self.lowpass(x)
57
+
58
+ return xx
bigvgan_activations.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 NVIDIA CORPORATION.
2
+ # Licensed under the MIT license.
3
+
4
+ # Adapted from https://github.com/NVIDIA/BigVGAN under the MIT license.
5
+ # LICENSE is in incl_licenses directory.
6
+
7
+ import torch
8
+ from torch import nn, sin, pow
9
+ from torch.nn import Parameter
10
+
11
+
12
+ class Snake(nn.Module):
13
+ """
14
+ Implementation of a sine-based periodic activation function
15
+ Shape:
16
+ - Input: (B, C, T)
17
+ - Output: (B, C, T), same shape as the input
18
+ Parameters:
19
+ - alpha - trainable parameter
20
+ References:
21
+ - This activation function is from this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda:
22
+ https://arxiv.org/abs/2006.08195
23
+ Examples:
24
+ >>> a1 = snake(256)
25
+ >>> x = torch.randn(256)
26
+ >>> x = a1(x)
27
+ """
28
+
29
+ def __init__(
30
+ self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False
31
+ ):
32
+ """
33
+ Initialization.
34
+ INPUT:
35
+ - in_features: shape of the input
36
+ - alpha: trainable parameter
37
+ alpha is initialized to 1 by default, higher values = higher-frequency.
38
+ alpha will be trained along with the rest of your model.
39
+ """
40
+ super(Snake, self).__init__()
41
+ self.in_features = in_features
42
+
43
+ # Initialize alpha
44
+ self.alpha_logscale = alpha_logscale
45
+ if self.alpha_logscale: # Log scale alphas initialized to zeros
46
+ self.alpha = Parameter(torch.zeros(in_features) * alpha)
47
+ else: # Linear scale alphas initialized to ones
48
+ self.alpha = Parameter(torch.ones(in_features) * alpha)
49
+
50
+ self.alpha.requires_grad = alpha_trainable
51
+
52
+ self.no_div_by_zero = 0.000000001
53
+
54
+ def forward(self, x):
55
+ """
56
+ Forward pass of the function.
57
+ Applies the function to the input elementwise.
58
+ Snake ∢= x + 1/a * sin^2 (xa)
59
+ """
60
+ alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # Line up with x to [B, C, T]
61
+ if self.alpha_logscale:
62
+ alpha = torch.exp(alpha)
63
+ x = x + (1.0 / (alpha + self.no_div_by_zero)) * pow(sin(x * alpha), 2)
64
+
65
+ return x
66
+
67
+
68
+ class SnakeBeta(nn.Module):
69
+ """
70
+ A modified Snake function which uses separate parameters for the magnitude of the periodic components
71
+ Shape:
72
+ - Input: (B, C, T)
73
+ - Output: (B, C, T), same shape as the input
74
+ Parameters:
75
+ - alpha - trainable parameter that controls frequency
76
+ - beta - trainable parameter that controls magnitude
77
+ References:
78
+ - This activation function is a modified version based on this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda:
79
+ https://arxiv.org/abs/2006.08195
80
+ Examples:
81
+ >>> a1 = snakebeta(256)
82
+ >>> x = torch.randn(256)
83
+ >>> x = a1(x)
84
+ """
85
+
86
+ def __init__(
87
+ self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False
88
+ ):
89
+ """
90
+ Initialization.
91
+ INPUT:
92
+ - in_features: shape of the input
93
+ - alpha - trainable parameter that controls frequency
94
+ - beta - trainable parameter that controls magnitude
95
+ alpha is initialized to 1 by default, higher values = higher-frequency.
96
+ beta is initialized to 1 by default, higher values = higher-magnitude.
97
+ alpha will be trained along with the rest of your model.
98
+ """
99
+ super(SnakeBeta, self).__init__()
100
+ self.in_features = in_features
101
+
102
+ # Initialize alpha
103
+ self.alpha_logscale = alpha_logscale
104
+ if self.alpha_logscale: # Log scale alphas initialized to zeros
105
+ self.alpha = Parameter(torch.zeros(in_features) * alpha)
106
+ self.beta = Parameter(torch.zeros(in_features) * alpha)
107
+ else: # Linear scale alphas initialized to ones
108
+ self.alpha = Parameter(torch.ones(in_features) * alpha)
109
+ self.beta = Parameter(torch.ones(in_features) * alpha)
110
+
111
+ self.alpha.requires_grad = alpha_trainable
112
+ self.beta.requires_grad = alpha_trainable
113
+
114
+ self.no_div_by_zero = 0.000000001
115
+
116
+ def forward(self, x):
117
+ """
118
+ Forward pass of the function.
119
+ Applies the function to the input elementwise.
120
+ SnakeBeta ∢= x + 1/b * sin^2 (xa)
121
+ """
122
+ alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # Line up with x to [B, C, T]
123
+ beta = self.beta.unsqueeze(0).unsqueeze(-1)
124
+ if self.alpha_logscale:
125
+ alpha = torch.exp(alpha)
126
+ beta = torch.exp(beta)
127
+ x = x + (1.0 / (beta + self.no_div_by_zero)) * pow(sin(x * alpha), 2)
128
+
129
+ return x
bigvgan_model.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 NVIDIA CORPORATION.
2
+ # Licensed under the MIT license.
3
+
4
+ # Adapted from https://github.com/NVIDIA/BigVGAN under the MIT license.
5
+ # LICENSE is in incl_licenses directory.
6
+
7
+ from typing import *
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+ from torch.nn import Conv1d, ConvTranspose1d
12
+ from torch.nn.utils import weight_norm, remove_weight_norm
13
+
14
+ from . import bigvgan_activations as activations
15
+ from .alias_free_act import Activation1d as TorchActivation1d
16
+ from .temporal_adapter import TemporalAdapterBlock
17
+
18
+
19
+ def init_weights(m, mean=0.0, std=0.01):
20
+ classname = m.__class__.__name__
21
+ if classname.find("Conv") != -1:
22
+ m.weight.data.normal_(mean, std)
23
+
24
+
25
+ def apply_weight_norm(m):
26
+ classname = m.__class__.__name__
27
+ if classname.find("Conv") != -1:
28
+ weight_norm(m)
29
+
30
+
31
+ def get_padding(kernel_size, dilation=1):
32
+ return int((kernel_size * dilation - dilation) / 2)
33
+
34
+
35
+ class FiLMBlock(nn.Module):
36
+ """Feature-wise Linear Modulation for watermark conditioning.
37
+
38
+ Maps a binary watermark vector to per-channel scale (Ξ³) and shift (Ξ²)
39
+ parameters that modulate intermediate feature maps.
40
+ """
41
+
42
+ def __init__(self, watermark_bits: int, channels: int, hidden_dim: int = 128):
43
+ super().__init__()
44
+ self.scale_net = nn.Sequential(
45
+ nn.Linear(watermark_bits, hidden_dim),
46
+ nn.ReLU(),
47
+ nn.Linear(hidden_dim, channels),
48
+ )
49
+ self.shift_net = nn.Sequential(
50
+ nn.Linear(watermark_bits, hidden_dim),
51
+ nn.ReLU(),
52
+ nn.Linear(hidden_dim, channels),
53
+ )
54
+ # Initialize close to identity: Ξ³β‰ˆ1, Ξ²β‰ˆ0
55
+ nn.init.zeros_(self.scale_net[-1].weight)
56
+ nn.init.zeros_(self.scale_net[-1].bias)
57
+ nn.init.zeros_(self.shift_net[-1].weight)
58
+ nn.init.zeros_(self.shift_net[-1].bias)
59
+
60
+ def forward(self, x, watermark):
61
+ """
62
+ Args:
63
+ x: [B, C, T] feature tensor
64
+ watermark: [B, watermark_bits] float tensor
65
+ Returns:
66
+ [B, C, T] modulated features
67
+ """
68
+ gamma = self.scale_net(watermark).unsqueeze(-1) # [B, C, 1]
69
+ beta = self.shift_net(watermark).unsqueeze(-1) # [B, C, 1]
70
+ return (1 + gamma) * x + beta # Ξ³β‰ˆ0 β†’ identity
71
+
72
+
73
+ class AMPBlock1(torch.nn.Module):
74
+ """
75
+ AMPBlock applies Snake / SnakeBeta activation functions with trainable parameters that control periodicity, defined for each layer.
76
+ AMPBlock1 has additional self.convs2 that contains additional Conv1d layers with a fixed dilation=1 followed by each layer in self.convs1
77
+
78
+ Args:
79
+ h (AttrDict): Hyperparameters.
80
+ channels (int): Number of convolution channels.
81
+ kernel_size (int): Size of the convolution kernel. Default is 3.
82
+ dilation (tuple): Dilation rates for the convolutions. Each dilation layer has two convolutions. Default is (1, 3, 5).
83
+ activation (str): Activation function type. Should be either 'snake' or 'snakebeta'. Default is None.
84
+ """
85
+
86
+ def __init__(
87
+ self,
88
+ channels: int,
89
+ kernel_size: int = 3,
90
+ dilation: tuple = (1, 3, 5),
91
+ activation: str = None,
92
+ snake_logscale: bool = True,
93
+ ):
94
+ super().__init__()
95
+
96
+
97
+ self.convs1 = nn.ModuleList(
98
+ [
99
+ weight_norm(
100
+ Conv1d(
101
+ channels,
102
+ channels,
103
+ kernel_size,
104
+ stride=1,
105
+ dilation=d,
106
+ padding=get_padding(kernel_size, d),
107
+ )
108
+ )
109
+ for d in dilation
110
+ ]
111
+ )
112
+ self.convs1.apply(init_weights)
113
+
114
+ self.convs2 = nn.ModuleList(
115
+ [
116
+ weight_norm(
117
+ Conv1d(
118
+ channels,
119
+ channels,
120
+ kernel_size,
121
+ stride=1,
122
+ dilation=1,
123
+ padding=get_padding(kernel_size, 1),
124
+ )
125
+ )
126
+ for _ in range(len(dilation))
127
+ ]
128
+ )
129
+ self.convs2.apply(init_weights)
130
+
131
+ self.num_layers = len(self.convs1) + len(
132
+ self.convs2
133
+ ) # Total number of conv layers
134
+
135
+ Activation1d = TorchActivation1d
136
+
137
+ # Activation functions
138
+ if activation == "snake":
139
+ self.activations = nn.ModuleList(
140
+ [
141
+ Activation1d(
142
+ activation=activations.Snake(
143
+ channels, alpha_logscale=snake_logscale
144
+ )
145
+ )
146
+ for _ in range(self.num_layers)
147
+ ]
148
+ )
149
+ elif activation == "snakebeta":
150
+ self.activations = nn.ModuleList(
151
+ [
152
+ Activation1d(
153
+ activation=activations.SnakeBeta(
154
+ channels, alpha_logscale=snake_logscale
155
+ )
156
+ )
157
+ for _ in range(self.num_layers)
158
+ ]
159
+ )
160
+ else:
161
+ raise NotImplementedError(
162
+ "activation incorrectly specified. check the config file and look for 'activation'."
163
+ )
164
+
165
+ def forward(self, x):
166
+ acts1, acts2 = self.activations[::2], self.activations[1::2]
167
+ for c1, c2, a1, a2 in zip(self.convs1, self.convs2, acts1, acts2):
168
+ xt = a1(x)
169
+ xt = c1(xt)
170
+ xt = a2(xt)
171
+ xt = c2(xt)
172
+ x = xt + x
173
+
174
+ return x
175
+
176
+ def remove_weight_norm(self):
177
+ for l in self.convs1:
178
+ remove_weight_norm(l)
179
+ for l in self.convs2:
180
+ remove_weight_norm(l)
181
+
182
+
183
+
184
+ class BigVGAN(torch.nn.Module):
185
+ """
186
+ BigVGAN is a neural vocoder model that applies anti-aliased periodic activation for residual blocks (resblocks).
187
+ New in BigVGAN-v2: it can optionally use optimized CUDA kernels for AMP (anti-aliased multi-periodicity) blocks.
188
+
189
+ Args:
190
+ h (AttrDict): Hyperparameters.
191
+ use_cuda_kernel (bool): If set to True, loads optimized CUDA kernels for AMP. This should be used for inference only, as training is not supported with CUDA kernels.
192
+
193
+ Note:
194
+ - The `use_cuda_kernel` parameter should be used for inference only, as training with CUDA kernels is not supported.
195
+ - Ensure that the activation function is correctly specified in the hyperparameters (h.activation).
196
+ """
197
+
198
+ def __init__(
199
+ self,
200
+ num_mels: int = 96,
201
+ global_channels: int = -1,
202
+ upsample_initial_channel: int = 1536,
203
+ resblock_kernel_sizes: List[int] = [3, 7, 11],
204
+ resblock_dilation_sizes: List[Tuple[int]] = [(1, 3, 5), (1, 3, 5), (1, 3, 5)],
205
+ upsample_rates: List[int] = [4,4,2,2,2,2],
206
+ upsample_kernel_sizes: List[int] = [8,8,4,4,4,4],
207
+ snake_logscale: bool = True,
208
+ activation: str = 'snakebeta',
209
+ use_bias_at_final:bool = False,
210
+ use_tanh_at_final:bool = False,
211
+ # Watermark FiLM conditioning
212
+ watermark_bits: int = 0,
213
+ watermark_film_hidden: int = 128,
214
+ # VocBulwark Temporal Adapter
215
+ vocbulwark_bits: int = 0,
216
+ vocbulwark_ta_hidden: int = 128,
217
+ vocbulwark_zero_conv_std: float = 0.02,
218
+ ):
219
+ super().__init__()
220
+
221
+ Activation1d = TorchActivation1d
222
+
223
+ self.num_kernels = len(resblock_kernel_sizes)
224
+ self.num_upsamples = len(upsample_rates)
225
+
226
+ # Pre-conv
227
+ self.conv_pre = weight_norm(
228
+ Conv1d(num_mels, upsample_initial_channel, 7, 1, padding=3)
229
+ )
230
+
231
+ if global_channels > 0:
232
+ self.global_conv = torch.nn.Conv1d(global_channels, upsample_initial_channel, 1)
233
+
234
+ # Define which AMPBlock to use. BigVGAN uses AMPBlock1 as default
235
+ resblock_class = AMPBlock1
236
+
237
+ # Transposed conv-based upsamplers. does not apply anti-aliasing
238
+ self.ups = nn.ModuleList()
239
+ for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)):
240
+ self.ups.append(
241
+ nn.ModuleList(
242
+ [
243
+ weight_norm(
244
+ ConvTranspose1d(
245
+ upsample_initial_channel // (2 ** i),
246
+ upsample_initial_channel // (2 ** (i + 1)),
247
+ k,
248
+ u,
249
+ padding=(k - u) // 2,
250
+ )
251
+ )
252
+ ]
253
+ )
254
+ )
255
+
256
+ # Residual blocks using anti-aliased multi-periodicity composition modules (AMP)
257
+ self.resblocks = nn.ModuleList()
258
+ for i in range(len(self.ups)):
259
+ ch = upsample_initial_channel // (2 ** (i + 1))
260
+ for j, (k, d) in enumerate(
261
+ zip(resblock_kernel_sizes, resblock_dilation_sizes)
262
+ ):
263
+ self.resblocks.append(
264
+ resblock_class(ch, k, d, activation=activation, snake_logscale=snake_logscale)
265
+ )
266
+
267
+ # Watermark FiLM layers (one per upsample stage)
268
+ self.watermark_bits = watermark_bits
269
+ if watermark_bits > 0:
270
+ self.film_layers = nn.ModuleList()
271
+ for i in range(len(self.ups)):
272
+ ch_film = upsample_initial_channel // (2 ** (i + 1))
273
+ self.film_layers.append(
274
+ FiLMBlock(watermark_bits, ch_film, watermark_film_hidden)
275
+ )
276
+
277
+ # VocBulwark Temporal Adapter layers (one per upsample stage)
278
+ self.vocbulwark_bits = vocbulwark_bits
279
+ if vocbulwark_bits > 0:
280
+ self.ta_layers = nn.ModuleList()
281
+ for i in range(len(self.ups)):
282
+ ch_ta = upsample_initial_channel // (2 ** (i + 1))
283
+ self.ta_layers.append(
284
+ TemporalAdapterBlock(vocbulwark_bits, ch_ta, vocbulwark_ta_hidden,
285
+ zero_conv_std=vocbulwark_zero_conv_std)
286
+ )
287
+
288
+ # Post-conv
289
+ activation_post = (
290
+ activations.Snake(ch, alpha_logscale=snake_logscale)
291
+ if activation == "snake"
292
+ else (
293
+ activations.SnakeBeta(ch, alpha_logscale=snake_logscale)
294
+ if activation == "snakebeta"
295
+ else None
296
+ )
297
+ )
298
+ if activation_post is None:
299
+ raise NotImplementedError(
300
+ "activation incorrectly specified. check the config file and look for 'activation'."
301
+ )
302
+
303
+ self.activation_post = Activation1d(activation=activation_post)
304
+
305
+ # Whether to use bias for the final conv_post. Default to True for backward compatibility
306
+ self.use_bias_at_final = use_bias_at_final
307
+ self.conv_post = weight_norm(
308
+ Conv1d(ch, 1, 7, 1, padding=3, bias=self.use_bias_at_final)
309
+ )
310
+
311
+ # Weight initialization
312
+ for i in range(len(self.ups)):
313
+ self.ups[i].apply(init_weights)
314
+ self.conv_post.apply(init_weights)
315
+
316
+ # Final tanh activation. Defaults to True for backward compatibility
317
+ self.use_tanh_at_final = use_tanh_at_final
318
+
319
+ def forward(
320
+ self,
321
+ x: torch.Tensor,
322
+ g: Optional[torch.Tensor] = None,
323
+ watermark: Optional[torch.Tensor] = None,
324
+ ):
325
+ # Pre-conv
326
+ x = self.conv_pre(x)
327
+ if g is not None:
328
+ x = x + self.global_conv(g)
329
+
330
+ for i in range(self.num_upsamples):
331
+ # Upsampling
332
+ for i_up in range(len(self.ups[i])):
333
+ x = self.ups[i][i_up](x)
334
+ # AMP blocks
335
+ xs = None
336
+ for j in range(self.num_kernels):
337
+ if xs is None:
338
+ xs = self.resblocks[i * self.num_kernels + j](x)
339
+ else:
340
+ xs += self.resblocks[i * self.num_kernels + j](x)
341
+ x = xs / self.num_kernels
342
+
343
+ # Watermark FiLM conditioning
344
+ if watermark is not None and self.watermark_bits > 0:
345
+ x = self.film_layers[i](x, watermark)
346
+
347
+ # VocBulwark Temporal Adapter conditioning
348
+ if watermark is not None and self.vocbulwark_bits > 0:
349
+ x = self.ta_layers[i](x, watermark)
350
+
351
+ # Post-conv
352
+ x = self.activation_post(x)
353
+ x = self.conv_post(x)
354
+ # Final tanh activation
355
+ if self.use_tanh_at_final:
356
+ x = torch.tanh(x)
357
+ else:
358
+ x = torch.clamp(x, min=-1.0, max=1.0) # Bound the output to [-1, 1]
359
+
360
+ return x
361
+
config.json ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_film_watermark": false,
3
+ "add_mel_loss": false,
4
+ "add_watermark_vocbulwark": true,
5
+ "add_wav2vec_loss": false,
6
+ "add_wavlm_loss": false,
7
+ "add_whisper_encoder_loss": false,
8
+ "add_whisper_feature_loss": false,
9
+ "add_whisper_kl_loss": false,
10
+ "architectures": [
11
+ "HiFiGANArchitecture"
12
+ ],
13
+ "block_out_channel_mults": [
14
+ 1,
15
+ 2,
16
+ 2,
17
+ 3
18
+ ],
19
+ "dtype": "float32",
20
+ "hifigan_bias": false,
21
+ "hifigan_channels": 1536,
22
+ "hifigan_global_channels": 256,
23
+ "hifigan_in_channels": 96,
24
+ "hifigan_kernel_size": 7,
25
+ "hifigan_nonlinear_activation": "snakebeta",
26
+ "hifigan_nonlinear_activation_params": {
27
+ "negative_slope": 0.1
28
+ },
29
+ "hifigan_out_channels": 1,
30
+ "hifigan_resblock_dilations": [
31
+ [
32
+ 1,
33
+ 3,
34
+ 5
35
+ ],
36
+ [
37
+ 1,
38
+ 3,
39
+ 5
40
+ ],
41
+ [
42
+ 1,
43
+ 3,
44
+ 5
45
+ ]
46
+ ],
47
+ "hifigan_resblock_kernel_sizes": [
48
+ 3,
49
+ 7,
50
+ 11
51
+ ],
52
+ "hifigan_upsample_kernel_sizes": [
53
+ 8,
54
+ 8,
55
+ 4,
56
+ 4,
57
+ 4,
58
+ 4
59
+ ],
60
+ "hifigan_upsample_scales": [
61
+ 4,
62
+ 4,
63
+ 2,
64
+ 2,
65
+ 2,
66
+ 2
67
+ ],
68
+ "hifigan_use_additional_convs": true,
69
+ "hifigan_use_weight_norm": true,
70
+ "hop_length": 256,
71
+ "input_type": "mel",
72
+ "lambda_adv": 1.0,
73
+ "lambda_feat_match": 10.0,
74
+ "lambda_mel": 15.0,
75
+ "lambda_vocbulwark_ext": 1.0,
76
+ "lambda_vocbulwark_mel": 0.1,
77
+ "lambda_vocbulwark_mstft": 0.1,
78
+ "lambda_vocbulwark_wavlm": 0.0,
79
+ "lambda_watermark": 1.0,
80
+ "lambda_wav2vec": 15.0,
81
+ "lambda_wavlm": 15.0,
82
+ "lambda_whisper_encoder": 15.0,
83
+ "lambda_whisper_features": 15.0,
84
+ "lambda_whisper_kl": 1.0,
85
+ "mel_channels": 96,
86
+ "model_type": "hifi_gan",
87
+ "mpd_reshapes": [
88
+ 2,
89
+ 3,
90
+ 5,
91
+ 7,
92
+ 11
93
+ ],
94
+ "mrd_resolutions": [
95
+ [
96
+ 1024,
97
+ 120,
98
+ 600
99
+ ],
100
+ [
101
+ 2048,
102
+ 240,
103
+ 1200
104
+ ],
105
+ [
106
+ 512,
107
+ 50,
108
+ 240
109
+ ]
110
+ ],
111
+ "n_fft": 1024,
112
+ "raw_sample_rate": 22050,
113
+ "snake_logscale": true,
114
+ "speaker_embed_checkpoint": null,
115
+ "speaker_embed_config": null,
116
+ "target_sample_rate": 24000,
117
+ "train_discriminator": false,
118
+ "transformers_version": "4.57.3",
119
+ "update_speaker_embedding": true,
120
+ "use_wavlm_features": false,
121
+ "vocbulwark_acc_tau1": 0.9,
122
+ "vocbulwark_acc_tau2": 0.95,
123
+ "vocbulwark_bits": 50,
124
+ "vocbulwark_bootstrap_threshold": 0.6,
125
+ "vocbulwark_fidelity_vs_clean": false,
126
+ "vocbulwark_ta_hidden": 128,
127
+ "vocbulwark_zero_conv_std": 0.02,
128
+ "watermark_bits": 32,
129
+ "watermark_film_hidden": 128,
130
+ "wav2vec_cnn_frozen": true,
131
+ "wav2vec_cnn_model_name": "facebook/wav2vec2-base",
132
+ "wav2vec_cnn_sample_rate": 16000,
133
+ "wavlm_layer_idx": 6,
134
+ "wavlm_model_name": "microsoft/wavlm-base-plus",
135
+ "win_length": 1024,
136
+ "speaker_embedding_size": 768,
137
+ "auto_map": {
138
+ "AutoConfig": "configuration_hifigan.HiFiGANConfig",
139
+ "AutoModel": "modeling_hifigan.HiFiGANArchitecture"
140
+ },
141
+ "fixed_watermark": [
142
+ 0,
143
+ 1,
144
+ 0,
145
+ 0,
146
+ 1,
147
+ 1,
148
+ 1,
149
+ 0,
150
+ 0,
151
+ 0,
152
+ 0,
153
+ 1,
154
+ 0,
155
+ 1,
156
+ 0,
157
+ 1,
158
+ 1,
159
+ 0,
160
+ 1,
161
+ 1,
162
+ 1,
163
+ 1,
164
+ 1,
165
+ 0,
166
+ 1,
167
+ 0,
168
+ 1,
169
+ 1,
170
+ 1,
171
+ 0,
172
+ 1,
173
+ 0,
174
+ 1,
175
+ 1,
176
+ 1,
177
+ 1,
178
+ 0,
179
+ 1,
180
+ 1,
181
+ 0,
182
+ 1,
183
+ 1,
184
+ 1,
185
+ 1,
186
+ 1,
187
+ 1,
188
+ 0,
189
+ 0,
190
+ 0,
191
+ 0
192
+ ]
193
+ }
configuration_hifigan.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+ from .speaker_embedding_config import SpeakerEmbeddingConfig
3
+
4
+
5
+ class HiFiGANConfig(PretrainedConfig):
6
+ model_type = "hifi_gan"
7
+
8
+ def __init__(
9
+ self,
10
+ # Audio settings
11
+ raw_sample_rate=22050,
12
+ target_sample_rate=24000,
13
+ n_fft=1024,
14
+ hop_length=256,
15
+ win_length=1024,
16
+ mel_channels=96,
17
+
18
+ # Speaker embedding
19
+ speaker_embed_config: SpeakerEmbeddingConfig = None,
20
+ speaker_embed_checkpoint: str = None,
21
+ update_speaker_embedding: bool = False,
22
+
23
+ # Input feature type: "mel" (default) or "wav2vec_cnn"
24
+ input_type: str = "mel",
25
+ wav2vec_cnn_model_name: str = "facebook/wav2vec2-base",
26
+ wav2vec_cnn_frozen: bool = True,
27
+ wav2vec_cnn_sample_rate: int = 16000,
28
+
29
+ # HiFi-GAN Generator config
30
+ hifigan_in_channels=96,
31
+ hifigan_out_channels=1,
32
+ hifigan_channels=512,
33
+ hifigan_global_channels=256, # Speaker conditioning
34
+ hifigan_kernel_size=7,
35
+ #hifigan_upsample_scales=[8, 8, 2, 2],
36
+ hifigan_upsample_kernel_sizes=[8,8,4,4,4,4],
37
+ #hifigan_upsample_kernel_sizes=[16, 16, 4, 4],
38
+ hifigan_upsample_scales=[4,4,2,2,2,2],
39
+ hifigan_resblock_kernel_sizes=[3, 7, 11],
40
+ hifigan_resblock_dilations=[[1, 3, 5], [1, 3, 5], [1, 3, 5]],
41
+ hifigan_use_additional_convs=True,
42
+ hifigan_bias=False,
43
+ hifigan_nonlinear_activation="snakebeta",
44
+ snake_logscale=True,
45
+ hifigan_nonlinear_activation_params={"negative_slope": 0.1},
46
+ hifigan_use_weight_norm=True,
47
+
48
+ mpd_reshapes=[2, 3, 5, 7, 11],
49
+ mrd_resolutions=[
50
+ (1024, 120, 600),
51
+ (2048, 240, 1200),
52
+ (512, 50, 240),
53
+ ],
54
+
55
+ # VAE config
56
+ block_out_channel_mults=[1, 2, 2, 3],
57
+
58
+ # Loss weights
59
+ lambda_adv=1.0, # Adversarial loss weight
60
+ lambda_feat_match=10.0, # Feature matching loss weight
61
+ add_mel_loss=True,
62
+ lambda_mel=15.0, # Mel spectrogram loss weight
63
+ lambda_wavlm=15.0, # WavLM perceptual loss weight
64
+ add_wavlm_loss=False,
65
+ add_wav2vec_loss=False,
66
+ lambda_wav2vec=15.0, # Wav2Vec perceptual loss weight
67
+ add_whisper_encoder_loss=False,
68
+ add_whisper_kl_loss=False,
69
+ lambda_whisper_encoder=15.0, # Whisper encoder hidden states L1 weight
70
+ lambda_whisper_kl=1.0, # Whisper decoder KL divergence weight
71
+ add_whisper_feature_loss=False,
72
+ lambda_whisper_features=15.0, # Whisper conv feature extractor L1 weight
73
+
74
+
75
+ use_wavlm_features: bool = False, # Use WavLM instead of MEL
76
+ wavlm_model_name: str = "microsoft/wavlm-base-plus",
77
+ wavlm_layer_idx: int = 6, # Which layer to extract features from (0-11 for base)
78
+
79
+
80
+ train_discriminator: bool = True,
81
+
82
+ # Watermark (FiLM)
83
+ add_film_watermark: bool = False,
84
+ watermark_bits: int = 32,
85
+ lambda_watermark: float = 1.0,
86
+ watermark_film_hidden: int = 128,
87
+
88
+ # VocBulwark watermarking
89
+ add_watermark_vocbulwark: bool = False,
90
+ vocbulwark_bits: int = 100,
91
+ vocbulwark_ta_hidden: int = 128,
92
+ lambda_vocbulwark_ext: float = 1.0,
93
+ lambda_vocbulwark_mel: float = 0.1,
94
+ lambda_vocbulwark_mstft: float = 0.1,
95
+ lambda_vocbulwark_wavlm: float = 0.0,
96
+ vocbulwark_acc_tau1: float = 0.9,
97
+ vocbulwark_acc_tau2: float = 0.95,
98
+ vocbulwark_bootstrap_threshold: float = 0.0,
99
+ vocbulwark_zero_conv_std: float = 0.02,
100
+ vocbulwark_fidelity_vs_clean: bool = False,
101
+
102
+ **kwargs
103
+ ):
104
+ super().__init__(**kwargs)
105
+
106
+ # Audio
107
+ self.raw_sample_rate = raw_sample_rate
108
+ self.target_sample_rate = target_sample_rate
109
+ self.n_fft = n_fft
110
+ self.hop_length = hop_length
111
+ self.win_length = win_length
112
+
113
+ # Mel
114
+ self.mel_channels = mel_channels
115
+
116
+ # Speaker
117
+ self.speaker_embed_config = speaker_embed_config
118
+ self.speaker_embed_checkpoint = speaker_embed_checkpoint
119
+ self.update_speaker_embedding = update_speaker_embedding
120
+
121
+ # Input type
122
+ self.input_type = input_type
123
+ self.wav2vec_cnn_model_name = wav2vec_cnn_model_name
124
+ self.wav2vec_cnn_frozen = wav2vec_cnn_frozen
125
+ self.wav2vec_cnn_sample_rate = wav2vec_cnn_sample_rate
126
+
127
+ # VAE parameters
128
+ self.block_out_channel_mults = block_out_channel_mults
129
+
130
+ # HiFi-GAN parameters
131
+ self.hifigan_in_channels = hifigan_in_channels
132
+ self.hifigan_out_channels = hifigan_out_channels
133
+ self.hifigan_channels = hifigan_channels
134
+ self.hifigan_global_channels = hifigan_global_channels
135
+ self.hifigan_kernel_size = hifigan_kernel_size
136
+ self.hifigan_upsample_scales = hifigan_upsample_scales
137
+ self.hifigan_upsample_kernel_sizes = hifigan_upsample_kernel_sizes
138
+ self.hifigan_resblock_kernel_sizes = hifigan_resblock_kernel_sizes
139
+ self.hifigan_resblock_dilations = hifigan_resblock_dilations
140
+ self.hifigan_use_additional_convs = hifigan_use_additional_convs
141
+ self.hifigan_bias = hifigan_bias
142
+ self.hifigan_nonlinear_activation = hifigan_nonlinear_activation
143
+ self.hifigan_nonlinear_activation_params = hifigan_nonlinear_activation_params
144
+ self.hifigan_use_weight_norm = hifigan_use_weight_norm
145
+ self.snake_logscale = snake_logscale
146
+
147
+ self.mpd_reshapes = mpd_reshapes
148
+ self.mrd_resolutions = mrd_resolutions
149
+
150
+ # Loss weights
151
+ self.lambda_adv = lambda_adv
152
+ self.lambda_feat_match = lambda_feat_match
153
+ self.add_mel_loss = add_mel_loss
154
+ self.lambda_mel = lambda_mel
155
+ self.lambda_wavlm = lambda_wavlm
156
+ self.add_wavlm_loss = add_wavlm_loss
157
+ self.add_wav2vec_loss = add_wav2vec_loss
158
+ self.lambda_wav2vec = lambda_wav2vec
159
+ self.add_whisper_encoder_loss = add_whisper_encoder_loss
160
+ self.add_whisper_kl_loss = add_whisper_kl_loss
161
+ self.lambda_whisper_encoder = lambda_whisper_encoder
162
+ self.lambda_whisper_kl = lambda_whisper_kl
163
+ self.add_whisper_feature_loss = add_whisper_feature_loss
164
+ self.lambda_whisper_features = lambda_whisper_features
165
+
166
+ self.use_wavlm_features = use_wavlm_features
167
+ self.wavlm_model_name = wavlm_model_name
168
+ self.wavlm_layer_idx = wavlm_layer_idx
169
+
170
+
171
+ # train discrimnator or not
172
+ self.train_discriminator = train_discriminator
173
+
174
+ # Watermark (FiLM)
175
+ self.add_film_watermark = add_film_watermark
176
+ self.watermark_bits = watermark_bits
177
+ self.lambda_watermark = lambda_watermark
178
+ self.watermark_film_hidden = watermark_film_hidden
179
+
180
+ # VocBulwark
181
+ self.add_watermark_vocbulwark = add_watermark_vocbulwark
182
+ self.vocbulwark_bits = vocbulwark_bits
183
+ self.vocbulwark_ta_hidden = vocbulwark_ta_hidden
184
+ self.lambda_vocbulwark_ext = lambda_vocbulwark_ext
185
+ self.lambda_vocbulwark_mel = lambda_vocbulwark_mel
186
+ self.lambda_vocbulwark_mstft = lambda_vocbulwark_mstft
187
+ self.lambda_vocbulwark_wavlm = lambda_vocbulwark_wavlm
188
+ self.vocbulwark_acc_tau1 = vocbulwark_acc_tau1
189
+ self.vocbulwark_acc_tau2 = vocbulwark_acc_tau2
190
+ self.vocbulwark_bootstrap_threshold = vocbulwark_bootstrap_threshold
191
+ self.vocbulwark_zero_conv_std = vocbulwark_zero_conv_std
192
+ self.vocbulwark_fidelity_vs_clean = vocbulwark_fidelity_vs_clean
example_roundtrip.ipynb ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# VocBulwark watermark round-trip\n",
8
+ "\n",
9
+ "The full pipeline, in three self-contained models loaded from the Hub:\n",
10
+ "\n",
11
+ "1. **Speaker encoder** β€” a reference clip β†’ a fixed-size speaker embedding (*whose* voice).\n",
12
+ "2. **Vocoder** β€” a mel spectrogram (*what* is said) + that embedding β†’ a 24 kHz waveform, with the fixed provenance watermark embedded automatically.\n",
13
+ "3. **Detector** β€” the waveform β†’ how many watermark bits match.\n",
14
+ "\n",
15
+ "In a real TTS system you usually already have the mel (from an acoustic model) and the speaker embedding (precomputed once per speaker), so step 1 is only needed to *make* an embedding from audio. Here we reconstruct one clip (use it as both content and speaker reference) to exercise the watermark.\n",
16
+ "\n",
17
+ "Needs only `torch`, `transformers`, `soundfile`, `torchaudio`. Put one or more `.wav` files in an `example_audio/` folder next to this notebook. If the repos are private, run `huggingface-cli login` first."
18
+ ]
19
+ },
20
+ {
21
+ "cell_type": "code",
22
+ "execution_count": null,
23
+ "metadata": {},
24
+ "outputs": [],
25
+ "source": [
26
+ "from pathlib import Path\n",
27
+ "\n",
28
+ "SPK_REPO = \"mlr2000/vocoder-large-speaker-encoder\" # <- speaker encoder\n",
29
+ "GEN_REPO = \"mlr2000/vocoder-large\" # <- vocoder (paired with SPK_REPO)\n",
30
+ "DET_REPO = \"mlr2000/vocoder-large-watermark-detector\" # <- matching detector\n",
31
+ "TGT_SR = 24000 # vocoder output SR\n",
32
+ "MAX_SAMPLES = 16 * TGT_SR\n",
33
+ "\n",
34
+ "AUDIO_DIR = next(p for p in [Path(\"example_audio\"), Path(\"../example_audio\")] if p.exists())"
35
+ ]
36
+ },
37
+ {
38
+ "cell_type": "code",
39
+ "execution_count": null,
40
+ "metadata": {},
41
+ "outputs": [],
42
+ "source": [
43
+ "import torch\n",
44
+ "from transformers import AutoModel\n",
45
+ "\n",
46
+ "enc = AutoModel.from_pretrained(SPK_REPO, trust_remote_code=True).eval()\n",
47
+ "gen = AutoModel.from_pretrained(GEN_REPO, trust_remote_code=True).eval()\n",
48
+ "det = AutoModel.from_pretrained(DET_REPO, trust_remote_code=True).eval()\n",
49
+ "RAW_SR = enc.config.raw_sample_rate # SR the speaker encoder expects\n",
50
+ "print(f\"embedding dim: {enc.config.embedding_size} | watermark: {len(gen.config.fixed_watermark)} bits\")"
51
+ ]
52
+ },
53
+ {
54
+ "cell_type": "code",
55
+ "execution_count": null,
56
+ "metadata": {},
57
+ "outputs": [],
58
+ "source": [
59
+ "import soundfile as sf\n",
60
+ "import torchaudio.functional as AF\n",
61
+ "from transformers import WhisperFeatureExtractor\n",
62
+ "\n",
63
+ "# Mel front-end β€” must match the trained vocoder (n_fft=1024, hop=256, mel channels from config).\n",
64
+ "N_MELS = gen.config.hifigan_in_channels\n",
65
+ "mel_fe = WhisperFeatureExtractor(sampling_rate=TGT_SR, n_fft=1024, feature_size=N_MELS, hop_length=256)\n",
66
+ "mel_fe.n_samples = MAX_SAMPLES\n",
67
+ "mel_fe.chunk_length = MAX_SAMPLES / TGT_SR\n",
68
+ "\n",
69
+ "def load_inputs(path):\n",
70
+ " \"\"\"A clip -> (mel = content, raw = speaker reference @ RAW_SR).\"\"\"\n",
71
+ " w, sr = sf.read(str(path))\n",
72
+ " w = torch.tensor(w, dtype=torch.float32)\n",
73
+ " if w.dim() == 2:\n",
74
+ " w = w.mean(1) # mono\n",
75
+ " raw = AF.resample(w, sr, RAW_SR) if sr != RAW_SR else w # speaker ref @ RAW_SR\n",
76
+ " tgt = AF.resample(raw, RAW_SR, TGT_SR) # content @ 24 kHz\n",
77
+ " mel = mel_fe(tgt.numpy(), sampling_rate=TGT_SR, padding=\"longest\",\n",
78
+ " return_tensors=\"pt\")[\"input_features\"]\n",
79
+ " return mel, raw.unsqueeze(0)"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "code",
84
+ "execution_count": null,
85
+ "metadata": {},
86
+ "outputs": [],
87
+ "source": [
88
+ "from IPython.display import Audio, display\n",
89
+ "\n",
90
+ "for path in sorted(AUDIO_DIR.glob(\"*.wav\")):\n",
91
+ " mel, raw = load_inputs(path)\n",
92
+ " with torch.no_grad():\n",
93
+ " emb = enc.embed(raw) # [1, embedding_size]\n",
94
+ " # The vocoder embeds its fixed watermark automatically.\n",
95
+ " audio = gen(mel_spectrogram=mel, speaker_embedding=emb).audio.squeeze(1)\n",
96
+ " r = det.detect(audio)\n",
97
+ " print(f\"{path.name}: {r['matches']}/{r['n_bits']} bits matched p={r['p_value']:.1e}\")\n",
98
+ " display(Audio(audio[0].numpy(), rate=TGT_SR))"
99
+ ]
100
+ },
101
+ {
102
+ "cell_type": "markdown",
103
+ "metadata": {},
104
+ "source": [
105
+ "Sanity check: an **unrelated** clip (here, random noise) is not from our model, so it matches only about half the bits with a large `p_value`."
106
+ ]
107
+ },
108
+ {
109
+ "cell_type": "code",
110
+ "execution_count": null,
111
+ "metadata": {},
112
+ "outputs": [],
113
+ "source": [
114
+ "print(\"random noise:\", det.detect(torch.randn(1, TGT_SR)))"
115
+ ]
116
+ }
117
+ ],
118
+ "metadata": {
119
+ "language_info": {
120
+ "name": "python"
121
+ }
122
+ },
123
+ "nbformat": 4,
124
+ "nbformat_minor": 5
125
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:626e334b29898f1b4840552666fd72f431baef10b4386fba4853a3310718e9e7
3
+ size 474989104
modeling_hifigan.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Self-contained inference model for the VocBulwark HiFi-GAN / BigVGAN vocoder.
2
+
3
+ Loadable with `AutoModel.from_pretrained(path, trust_remote_code=True)` without
4
+ the training repository. This is the lean vocoder: it takes a mel-spectrogram and
5
+ a precomputed speaker embedding, and does NOT include the speaker encoder (that
6
+ is a separate model), the training losses, discriminators, or watermark heads.
7
+ """
8
+ from dataclasses import dataclass
9
+
10
+ import torch
11
+ import torch.nn.utils.parametrize as parametrize
12
+ from torch.nn.utils import remove_weight_norm
13
+ from transformers import PreTrainedModel
14
+ from transformers.modeling_outputs import ModelOutput
15
+
16
+
17
+ def _strip_weight_norm(root):
18
+ """Remove every weight-norm reparametrization (both conventions), leaving
19
+ plain ``.weight`` parameters that match the folded export weights."""
20
+ for m in root.modules():
21
+ if parametrize.is_parametrized(m, "weight"):
22
+ parametrize.remove_parametrizations(m, "weight", leave_parametrized=True)
23
+ elif hasattr(m, "weight_g"):
24
+ try:
25
+ remove_weight_norm(m)
26
+ except (ValueError, RuntimeError):
27
+ pass
28
+
29
+ from .configuration_hifigan import HiFiGANConfig
30
+ from .bigvgan_model import BigVGAN
31
+
32
+ # transformers' trust_remote_code loader copies only modules *directly* imported
33
+ # by this entry file, and recognizes the `from .X import Y` form. Import a real
34
+ # symbol from every bundled leaf module so all files get copied into the
35
+ # dynamic-module cache (they are used transitively by the imports above).
36
+ from .bigvgan_activations import Snake as _Snake # noqa: F401
37
+ from .alias_free_act import Activation1d as _Activation1d # noqa: F401
38
+ from .alias_free_resample import UpSample1d as _UpSample1d # noqa: F401
39
+ from .alias_free_filter import LowPassFilter1d as _LowPassFilter1d # noqa: F401
40
+ from .temporal_adapter import TemporalAdapterBlock as _TemporalAdapterBlock # noqa: F401
41
+
42
+
43
+ @dataclass
44
+ class HiFiGANOutput(ModelOutput):
45
+ audio: torch.Tensor = None
46
+
47
+
48
+ class HiFiGANArchitecture(PreTrainedModel):
49
+ config_class = HiFiGANConfig
50
+
51
+ def __init__(self, config):
52
+ super().__init__(config)
53
+
54
+ # Lean vocoder: the speaker embedding is supplied at inference time, so the
55
+ # generator only needs its dimension to build the conditioning conv.
56
+ global_channels = int(getattr(config, "speaker_embedding_size", 0)) or -1
57
+ self.hifi_gan = BigVGAN(
58
+ num_mels=config.hifigan_in_channels,
59
+ upsample_initial_channel=config.hifigan_channels,
60
+ resblock_kernel_sizes=config.hifigan_resblock_kernel_sizes,
61
+ resblock_dilation_sizes=config.hifigan_resblock_dilations,
62
+ upsample_kernel_sizes=config.hifigan_upsample_kernel_sizes,
63
+ upsample_rates=config.hifigan_upsample_scales,
64
+ snake_logscale=config.snake_logscale,
65
+ activation=config.hifigan_nonlinear_activation,
66
+ use_bias_at_final=config.hifigan_bias,
67
+ global_channels=global_channels,
68
+ watermark_bits=config.watermark_bits if config.add_film_watermark else 0,
69
+ watermark_film_hidden=config.watermark_film_hidden,
70
+ vocbulwark_bits=config.vocbulwark_bits if config.add_watermark_vocbulwark else 0,
71
+ vocbulwark_ta_hidden=config.vocbulwark_ta_hidden,
72
+ vocbulwark_zero_conv_std=getattr(config, "vocbulwark_zero_conv_std", 0.02),
73
+ )
74
+
75
+ # Export folds weight-norm into plain .weight; strip the reparametrization
76
+ # here so module keys match (and inference is independent of torch's
77
+ # weight_norm convention).
78
+ _strip_weight_norm(self)
79
+
80
+ @torch.no_grad()
81
+ def forward(
82
+ self,
83
+ mel_spectrogram=None,
84
+ speaker_embedding=None,
85
+ input_features=None,
86
+ return_loss=False,
87
+ **kwargs,
88
+ ):
89
+ """Vocode a (log-)mel spectrogram into a waveform.
90
+
91
+ Args:
92
+ mel_spectrogram: [B, mel_channels, T] input features.
93
+ speaker_embedding: [B, speaker_embedding_size] speaker conditioning,
94
+ produced by the companion speaker-encoder model.
95
+ Returns:
96
+ HiFiGANOutput with `.audio` of shape [B, 1, samples] @ target_sample_rate.
97
+
98
+ The model's fixed provenance watermark is embedded in every generated clip
99
+ and cannot be changed or disabled through this interface.
100
+ """
101
+ hifi_gan_device = self.hifi_gan.conv_pre.bias.device
102
+ if torch.is_autocast_enabled():
103
+ target_dtype = torch.get_autocast_gpu_dtype()
104
+ else:
105
+ target_dtype = self.hifi_gan.conv_pre.bias.dtype
106
+
107
+ if input_features is not None:
108
+ features = input_features.to(device=hifi_gan_device, dtype=target_dtype)
109
+ else:
110
+ features = mel_spectrogram.to(device=hifi_gan_device, dtype=target_dtype)
111
+
112
+ if speaker_embedding is None:
113
+ raise ValueError(
114
+ "speaker_embedding is required: pass a "
115
+ "[B, config.speaker_embedding_size] tensor from the companion "
116
+ "speaker-encoder model.")
117
+ g = speaker_embedding.to(device=hifi_gan_device, dtype=target_dtype)
118
+ if g.dim() == 2: # [B, E] -> [B, E, 1]
119
+ g = g.unsqueeze(2)
120
+
121
+ # Provenance watermark: the model's fixed signature is embedded in every
122
+ # generated clip. It is intentionally not overridable through this
123
+ # interface β€” there is no way to disable or change it here.
124
+ watermark = None
125
+ fw = getattr(self.config, "fixed_watermark", None)
126
+ if fw is not None:
127
+ watermark = torch.tensor(fw, dtype=target_dtype, device=hifi_gan_device)
128
+ watermark = watermark.unsqueeze(0).expand(features.shape[0], -1)
129
+
130
+ audio = self.hifi_gan(features, g=g, watermark=watermark)
131
+ return HiFiGANOutput(audio=audio)
speaker_embedding_config.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers.models.wav2vec2.configuration_wav2vec2 import Wav2Vec2Config
2
+
3
+
4
+ class SpeakerEmbeddingConfig(Wav2Vec2Config):
5
+
6
+ def __init__(
7
+ self,
8
+ embedding_size: int = 768,
9
+ train_batch_speakers: int = 64,
10
+ train_batch_per_device: int = 64,
11
+ train_batch_samples_per_speaker: int = 10,
12
+ disable_positional_embeddings: bool = False,
13
+ loss_margin: float = 0.2,
14
+ loss_scale: float = 30.0,
15
+ use_layer_weights: bool = True,
16
+ n_projection_layers: int = 3,
17
+ **kwargs,
18
+ ):
19
+ super().__init__(**kwargs)
20
+ self.embedding_size = embedding_size
21
+ self.train_batch_per_device = train_batch_per_device
22
+ self.train_batch_speakers = train_batch_speakers
23
+ self.train_batch_samples_per_speaker = train_batch_samples_per_speaker
24
+ self.disable_positional_embeddings = disable_positional_embeddings
25
+ self.loss_margin = loss_margin
26
+ self.loss_scale = loss_scale
27
+ self.use_layer_weights = use_layer_weights
28
+ self.n_projection_layers = n_projection_layers
29
+ #self.conv_stride = (7, 2, 2, 2, 2, 2, 2)
30
+ #self.conv_kernel = (15, 5, 3, 3, 3, 2, 2)
31
+ #self.conv_dim = (512, 512, 512, 512, 512, 512, 512)
temporal_adapter.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Temporal Adapter (TA) from VocBulwark (Appendix B.1).
3
+
4
+ Lightweight module injected at each upsample stage of the frozen vocoder.
5
+ Embeds watermark bits into acoustic features via:
6
+ 1. Acoustic Feature Alignment: Emb(w) β†’ PFP β†’ w_proj ∈ [B, C, 1]
7
+ 2. Frame-level Temporal Broadcasting: w_proj β†’ w_latent ∈ [B, C, T]
8
+ 3. Adaptive Injection: concat(w_latent, h) β†’ downsample β†’ DSC β†’ zero_conv + h (residual)
9
+
10
+ Architecture from paper:
11
+ Embedding: Linear(bits, 2*C) β†’ LeakyReLU β†’ Linear(2*C, 512) β†’ LeakyReLU
12
+ PFP: Linear(512, rank) β†’ SiLU β†’ Linear(rank, C)
13
+ """
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+
18
+
19
+ class TemporalAdapterBlock(nn.Module):
20
+ """Single TA block for one upsample stage."""
21
+
22
+ def __init__(self, watermark_bits: int, channels: int, hidden_dim: int = 128,
23
+ zero_conv_std: float = 0.02):
24
+ super().__init__()
25
+ self.channels = channels
26
+
27
+ # Acoustic Feature Alignment (paper B.1):
28
+ # Two FC layers with two LeakyReLU activations.
29
+ # First FC projects to 2 * hidden_feature_channels, second to 512.
30
+ self.embedding = nn.Sequential(
31
+ nn.Linear(watermark_bits, channels * 2),
32
+ nn.LeakyReLU(0.2),
33
+ nn.Linear(channels * 2, 512),
34
+ nn.LeakyReLU(0.2),
35
+ )
36
+
37
+ # Progressive Feature Projection (PFP) (paper B.1):
38
+ # Two FC layers with SiLU activation between them.
39
+ # First FC reduces to pre-defined Rank, second aligns to C.
40
+ rank = hidden_dim // 2 # pre-defined rank value
41
+ self.pfp = nn.Sequential(
42
+ nn.Linear(512, rank),
43
+ nn.SiLU(),
44
+ nn.Linear(rank, channels),
45
+ )
46
+
47
+ # Adaptive Injection: concat [w_latent, h] β†’ downsample β†’ DSC β†’ zero_conv
48
+ # Downsample from 2C to C
49
+ self.downsample = nn.Conv1d(channels * 2, channels, kernel_size=1)
50
+
51
+ # Depth-wise Separable Convolution (DSC)
52
+ # BatchNorm is load-bearing: it normalizes the DSC output to ~unit variance
53
+ # before the small-init zero_conv, giving the injected watermark residual a
54
+ # meaningful scale. Without any norm the residual is ~0, the watermark barely
55
+ # perturbs the audio, extraction gradients vanish (grad_norm ~0.03) and
56
+ # training sticks at exactly random (acc 0.5, loss_ext = ln 2). InstanceNorm
57
+ # also fails (it cancels the time-constant watermark). So: keep BatchNorm.
58
+ self.dsc = nn.Sequential(
59
+ # Depth-wise conv
60
+ nn.Conv1d(channels, channels, kernel_size=3, padding=1, groups=channels),
61
+ nn.BatchNorm1d(channels),
62
+ nn.LeakyReLU(0.2),
63
+ # Point-wise conv
64
+ nn.Conv1d(channels, channels, kernel_size=1),
65
+ nn.BatchNorm1d(channels),
66
+ nn.LeakyReLU(0.2),
67
+ )
68
+
69
+ # Small-init convolution (not zero-init) to break the bootstrap deadlock.
70
+ # Zero-init prevents any watermark signal from reaching the audio,
71
+ # so the extractor can never learn. Small random init ensures different
72
+ # watermarks produce slightly different audio from the start.
73
+ self.zero_conv = nn.Conv1d(channels, channels, kernel_size=1)
74
+ nn.init.normal_(self.zero_conv.weight, std=zero_conv_std)
75
+ nn.init.zeros_(self.zero_conv.bias)
76
+
77
+ def forward(self, h: torch.Tensor, watermark: torch.Tensor) -> torch.Tensor:
78
+ """
79
+ Args:
80
+ h: [B, C, T] hidden features from vocoder upsample stage
81
+ watermark: [B, watermark_bits] binary watermark vector (float)
82
+ Returns:
83
+ [B, C, T] watermarked features (h + residual)
84
+ """
85
+ B, C, T = h.shape
86
+
87
+ # 1. Acoustic Feature Alignment
88
+ w_emb = self.embedding(watermark) # [B, 512]
89
+ w_proj = self.pfp(w_emb) # [B, C]
90
+
91
+ # 2. Frame-level Temporal Broadcasting
92
+ w_latent = w_proj.unsqueeze(-1).expand(B, C, T) # [B, C, T]
93
+
94
+ # 3. Adaptive Injection
95
+ h_cat = torch.cat([w_latent, h], dim=1) # [B, 2C, T]
96
+ h_down = self.downsample(h_cat) # [B, C, T]
97
+ h_dsc = self.dsc(h_down) # [B, C, T]
98
+ residual = self.zero_conv(h_dsc) # [B, C, T]
99
+
100
+ return h + residual