ALWOAL commited on
Commit
4a9b62b
·
verified ·
1 Parent(s): caa467a

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +11 -2
  2. app.py +139 -19
  3. core.py +44 -13
  4. lscodec/__init__.py +1 -0
  5. lscodec/bin/__init__.py +0 -0
  6. lscodec/bin/decode_feat_prompt.py +178 -0
  7. lscodec/bin/decode_wav_prompt.py +190 -0
  8. lscodec/bin/encode.py +143 -0
  9. lscodec/bin/recon_with_prompt.py +235 -0
  10. lscodec/bin/train.py +994 -0
  11. lscodec/datasets/perturbs.py +308 -0
  12. lscodec/datasets/scp_dataset.py +381 -0
  13. lscodec/distributed/__init__.py +0 -0
  14. lscodec/distributed/distrib.py +124 -0
  15. lscodec/distributed/launch.py +163 -0
  16. lscodec/layers/__init__.py +6 -0
  17. lscodec/layers/causal_conv.py +66 -0
  18. lscodec/layers/pqmf.py +150 -0
  19. lscodec/layers/residual_block.py +222 -0
  20. lscodec/layers/residual_stack.py +85 -0
  21. lscodec/layers/tade_res_block.py +160 -0
  22. lscodec/layers/upsample.py +194 -0
  23. lscodec/models/__init__.py +3 -0
  24. lscodec/models/conformer/__init__.py +1 -0
  25. lscodec/models/conformer/argument.py +87 -0
  26. lscodec/models/conformer/attention.py +308 -0
  27. lscodec/models/conformer/contextual_block_encoder_layer.py +309 -0
  28. lscodec/models/conformer/convolution.py +79 -0
  29. lscodec/models/conformer/decoder.py +247 -0
  30. lscodec/models/conformer/decoder_layer.py +175 -0
  31. lscodec/models/conformer/embedding.py +244 -0
  32. lscodec/models/conformer/encoder.py +247 -0
  33. lscodec/models/conformer/encoder_layer.py +157 -0
  34. lscodec/models/conformer/layer_norm.py +42 -0
  35. lscodec/models/conformer/multi_layer_conv.py +105 -0
  36. lscodec/models/conformer/nets_utils.py +498 -0
  37. lscodec/models/conformer/positionwise_feed_forward.py +32 -0
  38. lscodec/models/conformer/repeat.py +33 -0
  39. lscodec/models/conformer/subsampling.py +257 -0
  40. lscodec/models/conformer/swish.py +18 -0
  41. lscodec/models/conformer/vgg2l.py +89 -0
  42. lscodec/models/ctx_v2w.py +108 -0
  43. lscodec/models/fairseq_modules/__init__.py +0 -0
  44. lscodec/models/fairseq_modules/fp32_group_norm.py +133 -0
  45. lscodec/models/fairseq_modules/gumbel_vector_quantizer.py +212 -0
  46. lscodec/models/fairseq_modules/kmeans_vector_quantizer.py +206 -0
  47. lscodec/models/fairseq_modules/layer_norm.py +48 -0
  48. lscodec/models/fairseq_modules/transpose_last.py +21 -0
  49. lscodec/models/feature_extractor.py +178 -0
  50. lscodec/models/hifigan.py +732 -0
README.md CHANGED
@@ -8,7 +8,7 @@ sdk_version: 6.30.0
8
  app_file: app.py
9
  short_description: FocalCodec weights at int4–int32 vs fp4–fp32
10
  python_version: "3.12"
11
- startup_duration_timeout: 30m
12
  ---
13
 
14
  # ⚡ FocalCodec 12.5 Hz — weight quantization lab
@@ -28,7 +28,7 @@ The token stream is untouched, so the codec still runs at **0.1625 kbps**
28
  | --- | --- |
29
  | **Compare** | One reconstructed clip + input/reconstruction spectrograms, side by side for all 8 levels |
30
  | **Playground** | Encode → decode a single clip at one chosen level, with its metrics row |
31
- | **Benchmark** | SI-SDR, STOI, LSD, stored model size, bitrate and real-time factor for all 8 levels, plus a summary chart |
32
 
33
  ## Method
34
 
@@ -37,6 +37,10 @@ absmax scale, round-to-nearest in the target format, dequantize back to fp32, th
37
  the untouched FocalCodec forward pass. `fp4` is IEEE-style E2M1, `fp8` is E4M3, `fp16`
38
  and `fp32` are exact casts. Nothing is fine-tuned or calibrated on data.
39
 
 
 
 
 
40
  ⚠️ **On SI-SDR:** it is deeply negative at every level, including `fp32`. That is
41
  expected — at 0.16 kbps FocalCodec does not preserve waveform phase, and the
42
  [FocalCodec paper](https://arxiv.org/abs/2502.04465) reports SI-SNR ≈ −37 dB for the
@@ -48,3 +52,8 @@ untouched model. Use **STOI** (intelligibility, higher is better) and **LSD**
48
  Model, method and example audio by
49
  [Luca Della Libera](https://huggingface.co/lucadellalib) et al. — Apache-2.0.
50
  [FocalCodec: Low-Bitrate Speech Coding via Focal Modulation Networks](https://arxiv.org/abs/2502.04465), NeurIPS 2025.
 
 
 
 
 
 
8
  app_file: app.py
9
  short_description: FocalCodec weights at int4–int32 vs fp4–fp32
10
  python_version: "3.12"
11
+ startup_duration_timeout: 1h
12
  ---
13
 
14
  # ⚡ FocalCodec 12.5 Hz — weight quantization lab
 
28
  | --- | --- |
29
  | **Compare** | One reconstructed clip + input/reconstruction spectrograms, side by side for all 8 levels |
30
  | **Playground** | Encode → decode a single clip at one chosen level, with its metrics row |
31
+ | **Benchmark** | SI-SDR, STOI, LSD, stored model size, bitrate and real-time factor for all 8 levels, plus an `lscodec-25hz` baseline row and a summary chart |
32
 
33
  ## Method
34
 
 
37
  the untouched FocalCodec forward pass. `fp4` is IEEE-style E2M1, `fp8` is E4M3, `fp16`
38
  and `fp32` are exact casts. Nothing is fine-tuned or calibrated on data.
39
 
40
+ The Benchmark tab also runs [LSCodec-25 Hz](https://github.com/X-LANCE/LSCodec-Inference)
41
+ (self-prompted with WavLM-Large) as a second codec for reference: it is a different model
42
+ at a different bitrate (0.25 kbps vs 0.1625 kbps), so read it as a floor, not a control.
43
+
44
  ⚠️ **On SI-SDR:** it is deeply negative at every level, including `fp32`. That is
45
  expected — at 0.16 kbps FocalCodec does not preserve waveform phase, and the
46
  [FocalCodec paper](https://arxiv.org/abs/2502.04465) reports SI-SNR ≈ −37 dB for the
 
52
  Model, method and example audio by
53
  [Luca Della Libera](https://huggingface.co/lucadellalib) et al. — Apache-2.0.
54
  [FocalCodec: Low-Bitrate Speech Coding via Focal Modulation Networks](https://arxiv.org/abs/2502.04465), NeurIPS 2025.
55
+
56
+ The packed `int8` / `fp8` checkpoints produced by this lab live in
57
+ [`ALWOAL/focalcodec_12_5hz_quantized`](https://huggingface.co/ALWOAL/focalcodec_12_5hz_quantized).
58
+ The baseline codec is [LSCodec-Inference](https://github.com/X-LANCE/LSCodec-Inference) by
59
+ Yiwei Guo et al. — MIT.
app.py CHANGED
@@ -3,17 +3,22 @@
3
  import hashlib
4
  import time
5
 
6
- import spaces # must precede torch: patches torch.cuda for ZeroGPU
7
-
8
  import gradio as gr
 
9
  import soundfile as sf
 
10
  import torch
 
11
 
12
  import core
13
  from core import QUANT_LEVELS
14
 
15
  MODEL_REPO = "lucadellalib/focalcodec_12_5hz"
16
  HUB_REPO = "lucadellalib/focalcodec"
 
 
 
 
17
  MAX_SECONDS = 20
18
  HEADERS = ["Level", "SI-SDR (dB)", "STOI", "LSD (dB)", "Model size (MB)", "Bitrate (kbps)", "RTF"]
19
  DATATYPES = ["str", "number", "number", "number", "number", "number", "number"]
@@ -32,33 +37,125 @@ SAMPLE_RATE = codec.sample_rate_input
32
  assert codec.sample_rate_output == SAMPLE_RATE, "demo assumes a 16/16 kHz codec"
33
 
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  def _estimate_duration(audio_path: str, levels: tuple) -> float:
36
  try:
37
  seconds = min(sf.info(audio_path).duration, MAX_SECONDS)
38
- except Exception:
39
  seconds = float(MAX_SECONDS)
40
- return min(240.0, 5.0 + 2.0 * len(levels) + 2.5 * seconds)
 
41
 
42
 
43
  @spaces.GPU(duration=_estimate_duration)
44
  def _encode_decode(audio_path: str, levels: tuple) -> dict:
45
  """Reconstruct `audio_path` once per weight-quantization level, scoring each result."""
46
  ref, sr = core.load_audio(audio_path, SAMPLE_RATE, MAX_SECONDS)
47
- sig = torch.from_numpy(ref).unsqueeze(0).cuda()
48
- with torch.inference_mode(): # untimed warmup so the first row's RTF isn't inflated
49
- codec.sig_to_sig(sig)
50
  rows, reconstructed = [], {}
51
- for level in levels:
52
- codec.load_state_dict(BASE_STATE if level == "fp32" else core.quantize_state(BASE_STATE, level))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  torch.cuda.synchronize()
54
  start = time.perf_counter()
55
- with torch.inference_mode():
56
- rec = codec.sig_to_sig(sig).squeeze(0).float().cpu().numpy()
57
  torch.cuda.synchronize()
58
- rtf = (time.perf_counter() - start) / (len(ref) / sr)
59
- rows.append(core.evaluate(ref, rec, sr, rtf, SIZES[level], level))
60
- reconstructed[level] = (sr, rec)
61
- codec.load_state_dict(BASE_STATE)
62
  return {"input": (sr, ref), "rows": rows, "audio": reconstructed}
63
 
64
 
@@ -67,6 +164,7 @@ def _table(rows: list[dict]) -> list[list]:
67
 
68
 
69
  _CACHE: dict[str, dict] = {}
 
70
 
71
 
72
  def run(audio_path: str, levels: list | tuple) -> dict:
@@ -109,10 +207,23 @@ def playground(audio_path: str, level: str):
109
  )
110
 
111
 
 
 
 
 
 
 
 
 
 
 
 
112
  def benchmark(audio_path: str):
113
  """Score every quantization level on one input: metrics table plus summary chart."""
114
- result = run(audio_path, QUANT_LEVELS)
115
- return _table(result["rows"]), core.metrics_figure(result["rows"])
 
 
116
 
117
 
118
  HEADER = f"""# ⚡ FocalCodec 12.5 Hz — weight quantization lab
@@ -127,7 +238,9 @@ The bitrate never changes — **{core.BITRATE_KBPS:g} kbps** (12.5 tokens/s × 1
127
  """
128
 
129
  FOOTER = """Audio examples from the [FocalCodec repository](https://github.com/lucadellalib/focalcodec)
130
- (LibriSpeech, VoiceBank, MagicData). Model and method by Luca Della Libera et al., Apache-2.0."""
 
 
131
 
132
  with gr.Blocks(title="FocalCodec quantization lab") as demo:
133
  gr.Markdown(HEADER)
@@ -183,13 +296,20 @@ with gr.Blocks(title="FocalCodec quantization lab") as demo:
183
  sources=["upload", "microphone"], type="filepath", label="Input audio"
184
  )
185
  gr.Examples(examples=EXAMPLES, inputs=bm_input, label="Examples")
186
- bm_button = gr.Button("Run all 8 levels", variant="primary")
187
  with gr.Column(scale=3):
188
  gr.Markdown(
189
  "**STOI** ↑ intelligibility · **LSD** ↓ spectral error · "
190
  "**RTF** = encode+decode time ÷ audio duration · size assumes "
191
  "the stored weight format."
192
  )
 
 
 
 
 
 
 
193
  bm_table = gr.Dataframe(headers=HEADERS, datatype=DATATYPES, interactive=False)
194
  bm_plot = gr.Plot(label="Quality and size per level")
195
  bm_button.click(benchmark, inputs=bm_input, outputs=[bm_table, bm_plot])
 
3
  import hashlib
4
  import time
5
 
 
 
6
  import gradio as gr
7
+ import numpy as np
8
  import soundfile as sf
9
+ import spaces # patches torch.cuda for ZeroGPU; must stay above `import torch`
10
  import torch
11
+ from scipy.signal import resample_poly
12
 
13
  import core
14
  from core import QUANT_LEVELS
15
 
16
  MODEL_REPO = "lucadellalib/focalcodec_12_5hz"
17
  HUB_REPO = "lucadellalib/focalcodec"
18
+ LS_REPO = "cantabile-kwok/lscodec_25hz"
19
+ LS_LEVEL = "lscodec-25hz"
20
+ LS_SIZE_MB = 196.3 # encoder 70.4 + vocoder 125.6 + codebook 0.3 MB
21
+ LS_BITRATE_KBPS = 25 * 10 / 1000 # 25 tokens/s x 10-bit codebook
22
  MAX_SECONDS = 20
23
  HEADERS = ["Level", "SI-SDR (dB)", "STOI", "LSD (dB)", "Model size (MB)", "Bitrate (kbps)", "RTF"]
24
  DATATYPES = ["str", "number", "number", "number", "number", "number", "number"]
 
37
  assert codec.sample_rate_output == SAMPLE_RATE, "demo assumes a 16/16 kHz codec"
38
 
39
 
40
+ def _boot_lscodec() -> dict | None:
41
+ """LSCodec-25 Hz baseline: WavLM-Large (first 6 layers) prompt encoder + encoder/vocoder."""
42
+ from scipy import signal
43
+
44
+ if not hasattr(signal, "kaiser"): # their pqmf.py imports scipy<1.15's top-level kaiser
45
+ from scipy.signal.windows import kaiser
46
+
47
+ signal.kaiser = kaiser
48
+
49
+ from pathlib import Path
50
+
51
+ import yaml
52
+ from huggingface_hub import hf_hub_download
53
+ from transformers import WavLMModel
54
+
55
+ from lscodec.utils import load_model, load_vocoder
56
+
57
+ files = {
58
+ name: hf_hub_download(LS_REPO, name, repo_type="model")
59
+ for name in (
60
+ "encoder_config.yml",
61
+ "vocoder_config.yml",
62
+ "codebook.npy",
63
+ "lscodec_encoder.pt",
64
+ "lscodec_vocoder.pt",
65
+ )
66
+ }
67
+ encoder_config = yaml.safe_load(Path(files["encoder_config.yml"]).read_text())
68
+ vocoder_config = yaml.safe_load(Path(files["vocoder_config.yml"]).read_text())
69
+ encoder_config["pretrain_codebook"] = files["codebook.npy"]
70
+ vocoder_config["vq_codebook"] = files["codebook.npy"]
71
+
72
+ enc = load_model(encoder_config, files["lscodec_encoder.pt"]).eval().requires_grad_(False)
73
+ dec = load_vocoder(vocoder_config, files["lscodec_vocoder.pt"]).eval().requires_grad_(False)
74
+ codebook = torch.as_tensor(np.load(files["codebook.npy"], allow_pickle=True))
75
+ books = torch.nn.ModuleList(
76
+ [torch.nn.Embedding.from_pretrained(b, freeze=True) for b in (codebook if codebook.ndim == 3 else [codebook])]
77
+ )
78
+ wavlm = WavLMModel.from_pretrained("microsoft/wavlm-large").eval().requires_grad_(False)
79
+ wavlm.encoder.layers = wavlm.encoder.layers[:6] # prompt features come from layers 0..5
80
+ for model in (enc, dec, books, wavlm):
81
+ model.cuda() # ZeroGPU wants module-level cuda placement (docs: model loading)
82
+ return {
83
+ "enc": enc,
84
+ "dec": dec,
85
+ "books": books,
86
+ "wavlm": wavlm,
87
+ "repeat": bool(vocoder_config.get("repeat_input_tokens")),
88
+ "out_sr": int(vocoder_config["sampling_rate"]),
89
+ }
90
+
91
+
92
+ try:
93
+ LS = _boot_lscodec()
94
+ except Exception as exc: # noqa: BLE001 a third-party dep breaking must not take the demo down
95
+ LS = None
96
+ print(f"[lscodec] baseline disabled: {exc!r}")
97
+
98
+ BENCH_LEVELS = QUANT_LEVELS + ((LS_LEVEL,) if LS is not None else ())
99
+
100
+
101
+ def _lscodec_decode(ref: np.ndarray, sr: int) -> np.ndarray:
102
+ """Self-prompted LSCodec-25 Hz reconstruction, resampled back to `sr`."""
103
+ audio = torch.from_numpy(ref).float().view(1, -1).cuda()
104
+ with torch.inference_mode():
105
+ prompt_input = torch.nn.functional.layer_norm(audio, audio.shape) # fairseq WavLM cfg.normalize
106
+ prompt = LS["wavlm"](prompt_input, output_hidden_states=True).hidden_states[6]
107
+ _, _, index = LS["enc"].encode(audio.unsqueeze(0))
108
+ if LS["repeat"]:
109
+ index = index.repeat_interleave(2, dim=0)
110
+ vqvec = torch.cat([book(index[:, i]) for i, book in enumerate(LS["books"])], dim=-1).unsqueeze(0)
111
+ rec = LS["dec"].inference(vqvec, prompt)[-1].view(-1).float().cpu().numpy()
112
+ out_sr = LS["out_sr"]
113
+ if out_sr != sr:
114
+ g = np.gcd(out_sr, sr)
115
+ rec = resample_poly(rec, sr // g, out_sr // g).astype(np.float32)
116
+ return rec
117
+
118
+
119
  def _estimate_duration(audio_path: str, levels: tuple) -> float:
120
  try:
121
  seconds = min(sf.info(audio_path).duration, MAX_SECONDS)
122
+ except Exception: # noqa: BLE001 unreadable input falls back to the full cap
123
  seconds = float(MAX_SECONDS)
124
+ # ZeroGPU rejects a request above the visitor's tier cap, so never ask for more than 60 s.
125
+ return min(60.0, 4.0 + 2.0 * len(levels) + 2.0 * seconds + (8.0 if LS_LEVEL in levels else 0.0))
126
 
127
 
128
  @spaces.GPU(duration=_estimate_duration)
129
  def _encode_decode(audio_path: str, levels: tuple) -> dict:
130
  """Reconstruct `audio_path` once per weight-quantization level, scoring each result."""
131
  ref, sr = core.load_audio(audio_path, SAMPLE_RATE, MAX_SECONDS)
132
+ seconds = len(ref) / sr
 
 
133
  rows, reconstructed = [], {}
134
+ focal_levels = tuple(level for level in levels if level in QUANT_LEVELS)
135
+ if focal_levels:
136
+ sig = torch.from_numpy(ref).unsqueeze(0).cuda()
137
+ with torch.inference_mode(): # untimed warmup so the first row's RTF isn't inflated
138
+ codec.sig_to_sig(sig)
139
+ for level in focal_levels:
140
+ codec.load_state_dict(BASE_STATE if level == "fp32" else core.quantize_state(BASE_STATE, level))
141
+ torch.cuda.synchronize()
142
+ start = time.perf_counter()
143
+ with torch.inference_mode():
144
+ rec = codec.sig_to_sig(sig).squeeze(0).float().cpu().numpy()
145
+ torch.cuda.synchronize()
146
+ rtf = (time.perf_counter() - start) / seconds
147
+ rows.append(core.evaluate(ref, rec, sr, rtf, SIZES[level], level))
148
+ reconstructed[level] = (sr, rec)
149
+ codec.load_state_dict(BASE_STATE)
150
+ if LS_LEVEL in levels:
151
+ with torch.inference_mode(): # untimed warmup, as above
152
+ _lscodec_decode(ref, sr)
153
  torch.cuda.synchronize()
154
  start = time.perf_counter()
155
+ rec = _lscodec_decode(ref, sr)
 
156
  torch.cuda.synchronize()
157
+ rtf = (time.perf_counter() - start) / seconds
158
+ rows.append(core.evaluate(ref, rec, sr, rtf, LS_SIZE_MB, LS_LEVEL, LS_BITRATE_KBPS))
 
 
159
  return {"input": (sr, ref), "rows": rows, "audio": reconstructed}
160
 
161
 
 
164
 
165
 
166
  _CACHE: dict[str, dict] = {}
167
+ _LS_CACHE: dict[str, dict] = {}
168
 
169
 
170
  def run(audio_path: str, levels: list | tuple) -> dict:
 
207
  )
208
 
209
 
210
+ def _lscodec_row(audio_path: str) -> dict:
211
+ """LSCodec-25 Hz score for one input, cached separately from the quantization sweep."""
212
+ with open(audio_path, "rb") as fh:
213
+ key = hashlib.md5(fh.read()).hexdigest()
214
+ if key not in _LS_CACHE:
215
+ if len(_LS_CACHE) >= 6:
216
+ _LS_CACHE.clear()
217
+ _LS_CACHE[key] = _encode_decode(audio_path, (LS_LEVEL,))["rows"][0]
218
+ return _LS_CACHE[key]
219
+
220
+
221
  def benchmark(audio_path: str):
222
  """Score every quantization level on one input: metrics table plus summary chart."""
223
+ rows = run(audio_path, QUANT_LEVELS)["rows"]
224
+ if LS_LEVEL in BENCH_LEVELS:
225
+ rows = rows + [_lscodec_row(audio_path)]
226
+ return _table(rows), core.metrics_figure(rows)
227
 
228
 
229
  HEADER = f"""# ⚡ FocalCodec 12.5 Hz — weight quantization lab
 
238
  """
239
 
240
  FOOTER = """Audio examples from the [FocalCodec repository](https://github.com/lucadellalib/focalcodec)
241
+ (LibriSpeech, VoiceBank, MagicData). Model and method by Luca Della Libera et al., Apache-2.0.
242
+ The **lscodec-25hz** baseline is [LSCodec](https://github.com/X-LANCE/LSCodec-Inference) by
243
+ Yiwei Guo et al. (MIT), self-prompted with WavLM-Large from [`microsoft/wavlm-large`](https://huggingface.co/microsoft/wavlm-large)."""
244
 
245
  with gr.Blocks(title="FocalCodec quantization lab") as demo:
246
  gr.Markdown(HEADER)
 
296
  sources=["upload", "microphone"], type="filepath", label="Input audio"
297
  )
298
  gr.Examples(examples=EXAMPLES, inputs=bm_input, label="Examples")
299
+ bm_button = gr.Button(f"Run all {len(BENCH_LEVELS)} levels", variant="primary")
300
  with gr.Column(scale=3):
301
  gr.Markdown(
302
  "**STOI** ↑ intelligibility · **LSD** ↓ spectral error · "
303
  "**RTF** = encode+decode time ÷ audio duration · size assumes "
304
  "the stored weight format."
305
  )
306
+ if LS_LEVEL in BENCH_LEVELS:
307
+ gr.Markdown(
308
+ f"**{LS_LEVEL}** is a second codec, not a weight format: it runs at "
309
+ f"**{LS_BITRATE_KBPS:g} kbps** (25 tokens/s × 10 bits) versus FocalCodec's "
310
+ f"**{core.BITRATE_KBPS:g} kbps**, and its size counts only the codec "
311
+ f"({LS_SIZE_MB:g} MB) — the WavLM-Large prompt encoder it shares is 1.2 GB."
312
+ )
313
  bm_table = gr.Dataframe(headers=HEADERS, datatype=DATATYPES, interactive=False)
314
  bm_plot = gr.Plot(label="Quality and size per level")
315
  bm_button.click(benchmark, inputs=bm_input, outputs=[bm_table, bm_plot])
core.py CHANGED
@@ -24,6 +24,7 @@ BITRATE_KBPS = TOKEN_RATE_HZ * BITS_PER_TOKEN / 1000.0
24
 
25
  _BITS = {"int4": 4, "int8": 8, "int16": 16, "int32": 32, "fp4": 4, "fp8": 8, "fp16": 16, "fp32": 32}
26
  _FP_FORMATS = {"fp4": (2, 1), "fp8": (4, 3)} # (exponent bits, mantissa bits) -> E2M1 / E4M3
 
27
 
28
 
29
  def _ref_scale(w: torch.Tensor) -> torch.Tensor:
@@ -33,24 +34,40 @@ def _ref_scale(w: torch.Tensor) -> torch.Tensor:
33
  return w.abs().amax()
34
 
35
 
36
- def _quant_int(w: torch.Tensor, bits: int) -> torch.Tensor:
37
- if bits >= 32:
38
- return w
 
 
 
39
  qmax = 1 << (bits - 1)
40
- scale = (_ref_scale(w) / qmax).clamp_min(1e-12)
41
- return (w / scale).round().clamp(-qmax, qmax - 1) * scale
 
 
 
42
 
43
 
44
- def _quant_fp(w: torch.Tensor, e_bits: int, m_bits: int) -> torch.Tensor:
 
 
45
  bias = (1 << (e_bits - 1)) - 1
46
  max_exp = (1 << e_bits) - 1 - bias
47
- max_norm = (2.0 - 2.0 ** -m_bits) * 2.0**max_exp
48
- scale = (_ref_scale(w) / max_norm).clamp_min(1e-12)
49
- x = w / scale
50
  ax = x.abs()
51
  exp = torch.floor(torch.log2(ax.clamp_min(1e-30))).clamp(1 - bias, max_exp)
52
  step = torch.pow(2.0, exp - m_bits)
53
- return torch.sign(x) * ((ax / step).round() * step) * scale
 
 
 
 
 
 
 
 
 
 
54
 
55
 
56
  def quantize_state(state: dict, level: str) -> dict:
@@ -66,7 +83,7 @@ def quantize_state(state: dict, level: str) -> dict:
66
  elif level.startswith("int"):
67
  out[key] = _quant_int(value, int(level[3:]))
68
  else:
69
- out[key] = _quant_fp(value, *_FP_FORMATS[level])
70
  return out
71
 
72
 
@@ -104,16 +121,21 @@ def log_spectral_distance(ref: np.ndarray, rec: np.ndarray, sr: int) -> float:
104
  return float(np.mean(np.sqrt(np.mean(diff**2, axis=0))))
105
 
106
 
107
- def evaluate(ref: np.ndarray, rec: np.ndarray, sr: int, rtf: float, size_mb: float, level: str) -> dict:
 
 
 
108
  n = min(len(ref), len(rec))
109
  ref, rec = ref[:n], rec[:n]
 
 
110
  return {
111
  "Level": level,
112
  "SI-SDR (dB)": round(si_sdr(ref, rec), 2),
113
  "STOI": round(float(stoi(ref, rec, sr, extended=False)), 3),
114
  "LSD (dB)": round(log_spectral_distance(ref, rec, sr), 2),
115
  "Model size (MB)": round(size_mb, 1),
116
- "Bitrate (kbps)": round(BITRATE_KBPS, 4),
117
  "RTF": round(rtf, 3),
118
  }
119
 
@@ -150,9 +172,18 @@ if __name__ == "__main__":
150
  q = quantize_state({"w": w}, level)["w"]
151
  assert torch.isfinite(q).all(), f"{level} not finite"
152
  assert q.abs().max() <= w.abs().max() * 1.01 + 1e-9, f"{level} broke the magnitude bound"
 
 
 
 
 
 
 
 
153
  x = np.random.randn(16000).astype(np.float32)
154
  assert si_sdr(x, x) > 60, "SI-SDR of identical signals should be large"
155
  assert si_sdr(x, 3 * x) > 60, "SI-SDR must be scale invariant"
156
  assert abs(evaluate(x, x, 16000, 1.0, 1.0, "fp32")["STOI"] - 1.0) < 1e-3
157
  assert evaluate(x, x, 16000, 1.0, 1.0, "fp32")["Bitrate (kbps)"] == round(BITRATE_KBPS, 4)
 
158
  print("core self-check ok")
 
24
 
25
  _BITS = {"int4": 4, "int8": 8, "int16": 16, "int32": 32, "fp4": 4, "fp8": 8, "fp16": 16, "fp32": 32}
26
  _FP_FORMATS = {"fp4": (2, 1), "fp8": (4, 3)} # (exponent bits, mantissa bits) -> E2M1 / E4M3
27
+ _FP_MAX_NORM = {"fp4": 6.0, "fp8": 448.0} # E2M1 max; E4M3-FN finite max (top encoding is NaN)
28
 
29
 
30
  def _ref_scale(w: torch.Tensor) -> torch.Tensor:
 
34
  return w.abs().amax()
35
 
36
 
37
+ def int_scale(w: torch.Tensor, bits: int) -> torch.Tensor:
38
+ return (_ref_scale(w) / (1 << (bits - 1))).clamp_min(1e-12)
39
+
40
+
41
+ def int_levels(w: torch.Tensor, bits: int) -> torch.Tensor:
42
+ """Signed integer levels before rescaling (what an int-format export stores)."""
43
  qmax = 1 << (bits - 1)
44
+ return (w / int_scale(w, bits)).round().clamp(-qmax, qmax - 1)
45
+
46
+
47
+ def fp_scale(w: torch.Tensor, level: str) -> torch.Tensor:
48
+ return (_ref_scale(w) / _FP_MAX_NORM[level]).clamp_min(1e-12)
49
 
50
 
51
+ def fp_levels(w: torch.Tensor, level: str) -> torch.Tensor:
52
+ """Values on the format's own grid (what a float-format export stores, unscaled)."""
53
+ e_bits, m_bits = _FP_FORMATS[level]
54
  bias = (1 << (e_bits - 1)) - 1
55
  max_exp = (1 << e_bits) - 1 - bias
56
+ x = w / fp_scale(w, level)
 
 
57
  ax = x.abs()
58
  exp = torch.floor(torch.log2(ax.clamp_min(1e-30))).clamp(1 - bias, max_exp)
59
  step = torch.pow(2.0, exp - m_bits)
60
+ return torch.sign(x) * ((ax / step).round() * step)
61
+
62
+
63
+ def _quant_int(w: torch.Tensor, bits: int) -> torch.Tensor:
64
+ if bits >= 32:
65
+ return w
66
+ return int_levels(w, bits) * int_scale(w, bits)
67
+
68
+
69
+ def _quant_fp(w: torch.Tensor, level: str) -> torch.Tensor:
70
+ return fp_levels(w, level) * fp_scale(w, level)
71
 
72
 
73
  def quantize_state(state: dict, level: str) -> dict:
 
83
  elif level.startswith("int"):
84
  out[key] = _quant_int(value, int(level[3:]))
85
  else:
86
+ out[key] = _quant_fp(value, level)
87
  return out
88
 
89
 
 
121
  return float(np.mean(np.sqrt(np.mean(diff**2, axis=0))))
122
 
123
 
124
+ def evaluate(
125
+ ref: np.ndarray, rec: np.ndarray, sr: int, rtf: float, size_mb: float, level: str,
126
+ bitrate_kbps: float | None = None,
127
+ ) -> dict:
128
  n = min(len(ref), len(rec))
129
  ref, rec = ref[:n], rec[:n]
130
+ if bitrate_kbps is None:
131
+ bitrate_kbps = BITRATE_KBPS
132
  return {
133
  "Level": level,
134
  "SI-SDR (dB)": round(si_sdr(ref, rec), 2),
135
  "STOI": round(float(stoi(ref, rec, sr, extended=False)), 3),
136
  "LSD (dB)": round(log_spectral_distance(ref, rec, sr), 2),
137
  "Model size (MB)": round(size_mb, 1),
138
+ "Bitrate (kbps)": round(bitrate_kbps, 4),
139
  "RTF": round(rtf, 3),
140
  }
141
 
 
172
  q = quantize_state({"w": w}, level)["w"]
173
  assert torch.isfinite(q).all(), f"{level} not finite"
174
  assert q.abs().max() <= w.abs().max() * 1.01 + 1e-9, f"{level} broke the magnitude bound"
175
+ # the packed levels a real export stores must reproduce the fake-quant demo exactly
176
+ assert torch.equal(int_levels(w, 8) * int_scale(w, 8), quantize_state({"w": w}, "int8")["w"])
177
+ assert torch.equal(fp_levels(w, "fp8") * fp_scale(w, "fp8"), quantize_state({"w": w}, "fp8")["w"])
178
+ # fp8 grid values are exactly float8_e4m3fn-representable, so no precision is lost on store
179
+ grid8 = fp_levels(w, "fp8")
180
+ assert torch.equal(grid8.to(torch.float8_e4m3fn).float(), grid8), "fp8 grid escapes E4M3-FN"
181
+ grid4 = fp_levels(w, "fp4")
182
+ assert grid4.abs().max() <= _FP_MAX_NORM["fp4"] * 1.0000001, "fp4 grid escapes E2M1"
183
  x = np.random.randn(16000).astype(np.float32)
184
  assert si_sdr(x, x) > 60, "SI-SDR of identical signals should be large"
185
  assert si_sdr(x, 3 * x) > 60, "SI-SDR must be scale invariant"
186
  assert abs(evaluate(x, x, 16000, 1.0, 1.0, "fp32")["STOI"] - 1.0) < 1e-3
187
  assert evaluate(x, x, 16000, 1.0, 1.0, "fp32")["Bitrate (kbps)"] == round(BITRATE_KBPS, 4)
188
+ assert evaluate(x, x, 16000, 1.0, 1.0, "lscodec-25hz", 0.25)["Bitrate (kbps)"] == 0.25
189
  print("core self-check ok")
lscodec/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # -*- coding: utf-8 -*-
lscodec/bin/__init__.py ADDED
File without changes
lscodec/bin/decode_feat_prompt.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Tomoki Hayashi
5
+ # MIT License (https://opensource.org/licenses/MIT)
6
+
7
+ # MIT License
8
+ # Copyright (c) 2025 Yiwei Guo
9
+ # This file is based on ParallelWaveGAN (https://github.com/kan-bayashi/ParallelWaveGAN).
10
+ # Above is the original copyright notice.
11
+
12
+
13
+ """Decode with Trained LSCodec Vocoder."""
14
+
15
+ import argparse
16
+ import logging
17
+ import os
18
+ import time
19
+
20
+ import numpy as np
21
+ import soundfile as sf
22
+ import torch
23
+ import yaml
24
+ import kaldiio
25
+ from tqdm import tqdm
26
+
27
+ from lscodec.utils import load_vocoder
28
+
29
+
30
+ def main():
31
+ """Run decoding process."""
32
+ parser = argparse.ArgumentParser(
33
+ description="Decode from LSCodec tokens to waveforms"
34
+ )
35
+ parser.add_argument(
36
+ "--feats-scp",
37
+ "--scp",
38
+ default=None,
39
+ type=str,
40
+ help="kaldi-style feats.scp file. "
41
+ "you need to specify either feats-scp or dumpdir.",
42
+ )
43
+ parser.add_argument(
44
+ "--prompt-scp",
45
+ default=None,
46
+ type=str
47
+ )
48
+ parser.add_argument(
49
+ "--outdir",
50
+ type=str,
51
+ required=True,
52
+ help="directory to save generated speech.",
53
+ )
54
+ # parser.add_argument(
55
+ # "--checkpoint",
56
+ # type=str,
57
+ # default='pretrained/lscodec_vocoder.pt',
58
+ # help="checkpoint file to be loaded.",
59
+ # )
60
+ # parser.add_argument(
61
+ # "--config",
62
+ # default='pretrained/vocoder_config.yml',
63
+ # type=str,
64
+ # help="yaml format configuration file."
65
+ # )
66
+ parser.add_argument(
67
+ '--pretrained-dir',
68
+ type=str,
69
+ default='pretrained',
70
+ help='directory of pretrained models. Should contain WavLM-Large.pt, lscodec_vocoder.pt, vocoder_config.yml',
71
+ )
72
+ parser.add_argument(
73
+ "--verbose",
74
+ type=int,
75
+ default=1,
76
+ help="logging level. higher is more logging. (default=1)",
77
+ )
78
+ args = parser.parse_args()
79
+
80
+ # set logger
81
+ if args.verbose > 1:
82
+ logging.basicConfig(
83
+ level=logging.DEBUG,
84
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
85
+ )
86
+ elif args.verbose > 0:
87
+ logging.basicConfig(
88
+ level=logging.INFO,
89
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
90
+ )
91
+ else:
92
+ logging.basicConfig(
93
+ level=logging.WARN,
94
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
95
+ )
96
+ logging.warning("Skip DEBUG/INFO messages")
97
+
98
+ # check directory existence
99
+ if not os.path.exists(args.outdir):
100
+ os.makedirs(args.outdir)
101
+
102
+ # load config
103
+ config_path = os.path.join(args.pretrained_dir, "vocoder_config.yml")
104
+ checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_vocoder.pt")
105
+ with open(config_path) as f:
106
+ config = yaml.load(f, Loader=yaml.Loader)
107
+ config.update(vars(args))
108
+ config['vq_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
109
+
110
+ # check arguments
111
+ if args.feats_scp is None:
112
+ raise ValueError("Please specify --feats-scp.")
113
+
114
+ # setup model
115
+ if torch.cuda.is_available():
116
+ device = torch.device("cuda")
117
+ logging.info("Using GPU.")
118
+ else:
119
+ device = torch.device("cpu")
120
+ logging.info("Using CPU.")
121
+ model = load_vocoder(checkpoint=checkpoint_path, config=config)
122
+ logging.info(f"Loaded model parameters from {checkpoint_path}.")
123
+ model.backend.remove_weight_norm()
124
+ model = model.eval().to(device)
125
+
126
+ # load vq codebook
127
+ feat_codebook = torch.tensor(np.load(config["vq_codebook"], allow_pickle=True)).to(device) # (V, D)
128
+ if feat_codebook.ndim == 2:
129
+ feat_codebook = feat_codebook.unsqueeze(0)
130
+ feat_codebook_numgroups = feat_codebook.shape[0]
131
+ feat_codebook = torch.nn.ModuleList([torch.nn.Embedding.from_pretrained(feat_codebook[i], freeze=True) for i in range(feat_codebook_numgroups)])
132
+
133
+ # start generation
134
+ total_rtf = 0.0
135
+ feats_scp = kaldiio.load_scp(args.feats_scp)
136
+ prompt_scp = kaldiio.load_scp(args.prompt_scp)
137
+ total_cnt = len(list(feats_scp.keys()))
138
+ logging.info(f"The number of features to be decoded = {total_cnt}.")
139
+
140
+ with torch.no_grad(), tqdm(feats_scp, desc="[decode]", total=total_cnt) as pbar:
141
+ for idx, utt_id in enumerate(pbar, 1):
142
+ c = feats_scp[utt_id]
143
+ prompt = prompt_scp[utt_id]
144
+
145
+ c = torch.tensor(c).to(device) # (L, D)
146
+ prompt = torch.tensor(prompt).unsqueeze(0).to(device) # (1, L', D')
147
+
148
+ vqidx = c.long()
149
+ if config.get("repeat_input_tokens", False):
150
+ vqidx = vqidx.repeat_interleave(2, dim=0)
151
+ vqvec = torch.cat([feat_codebook[i](vqidx[:, i]) for i in range(feat_codebook_numgroups)], dim=-1).unsqueeze(0) # (1, L, D)
152
+
153
+ # generate
154
+ start = time.time()
155
+ y = model.inference(vqvec, prompt)[-1].view(-1)
156
+ rtf = (time.time() - start) / (len(y) / config["sampling_rate"])
157
+ pbar.set_postfix({"RTF": rtf})
158
+ total_rtf += rtf
159
+
160
+ tgt_dir = os.path.dirname(os.path.join(config["outdir"], f"{utt_id}.wav"))
161
+ os.makedirs(tgt_dir, exist_ok=True)
162
+ basename = os.path.basename(f"{utt_id}.wav")
163
+ # save as PCM 16 bit wav file
164
+ sf.write(
165
+ os.path.join(tgt_dir, basename),
166
+ y.cpu().numpy(),
167
+ config["sampling_rate"],
168
+ "PCM_16",
169
+ )
170
+
171
+ # report average RTF
172
+ logging.info(
173
+ f"Finished generation of {idx} utterances (RTF = {total_rtf / idx:.03f})."
174
+ )
175
+
176
+
177
+ if __name__ == "__main__":
178
+ main()
lscodec/bin/decode_wav_prompt.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Tomoki Hayashi
5
+ # MIT License (https://opensource.org/licenses/MIT)
6
+
7
+ # MIT License
8
+ # Copyright (c) 2025 Yiwei Guo
9
+ # This file is based on ParallelWaveGAN (https://github.com/kan-bayashi/ParallelWaveGAN).
10
+ # Above is the original copyright notice.
11
+
12
+ """Decode with Trained LSCodec Vocoder."""
13
+
14
+ import argparse
15
+ import logging
16
+ import os
17
+ import time
18
+
19
+ import numpy as np
20
+ import soundfile as sf
21
+ import torch
22
+ import yaml
23
+ import kaldiio
24
+ from tqdm import tqdm
25
+ import librosa
26
+
27
+ from lscodec.utils import load_vocoder, load_audio
28
+ from lscodec.ssl_models.wavlm_extractor import Extractor as WavLMExtractor
29
+
30
+
31
+ def main():
32
+ """Run decoding process."""
33
+ parser = argparse.ArgumentParser(
34
+ description="Decode from LSCodec tokens to waveforms"
35
+ )
36
+ parser.add_argument(
37
+ "--feats-scp",
38
+ "--scp",
39
+ default=None,
40
+ type=str,
41
+ help="kaldi-style feats.scp file. "
42
+ "you need to specify either feats-scp or dumpdir.",
43
+ )
44
+ parser.add_argument(
45
+ "--prompt-wav-scp",
46
+ default=None,
47
+ type=str,
48
+ help="kaldi-style prompt_wav.scp file."
49
+ )
50
+ parser.add_argument(
51
+ "--num-frames",
52
+ default=None,
53
+ type=str
54
+ )
55
+ parser.add_argument(
56
+ "--outdir",
57
+ type=str,
58
+ required=True,
59
+ help="directory to save generated speech.",
60
+ )
61
+ parser.add_argument(
62
+ '--pretrained-dir',
63
+ type=str,
64
+ default='pretrained',
65
+ help='directory to save pretrained models. Should contain WavLM-Large.pt, lscodec_vocoder.pt, vocoder_config.yml',
66
+ )
67
+ parser.add_argument(
68
+ "--verbose",
69
+ type=int,
70
+ default=1,
71
+ help="logging level. higher is more logging. (default=1)",
72
+ )
73
+ args = parser.parse_args()
74
+
75
+ # set logger
76
+ if args.verbose > 1:
77
+ logging.basicConfig(
78
+ level=logging.DEBUG,
79
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
80
+ )
81
+ elif args.verbose > 0:
82
+ logging.basicConfig(
83
+ level=logging.INFO,
84
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
85
+ )
86
+ else:
87
+ logging.basicConfig(
88
+ level=logging.WARN,
89
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
90
+ )
91
+ logging.warning("Skip DEBUG/INFO messages")
92
+
93
+ # check directory existence
94
+ if not os.path.exists(args.outdir):
95
+ os.makedirs(args.outdir)
96
+
97
+ # load config
98
+ config_path = os.path.join(args.pretrained_dir, "vocoder_config.yml")
99
+ checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_vocoder.pt")
100
+ wavlm_path = os.path.join(args.pretrained_dir, "WavLM-Large.pt")
101
+ with open(config_path) as f:
102
+ config = yaml.load(f, Loader=yaml.Loader)
103
+ config.update(vars(args))
104
+ config['vq_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
105
+
106
+ # check arguments
107
+ if args.feats_scp is None:
108
+ raise ValueError("Please specify --feats-scp.")
109
+
110
+ # setup model
111
+ if torch.cuda.is_available():
112
+ device = torch.device("cuda")
113
+ logging.info("Using GPU.")
114
+ else:
115
+ device = torch.device("cpu")
116
+ logging.info("Using CPU.")
117
+ model = load_vocoder(checkpoint=checkpoint_path, config=config)
118
+ logging.info(f"Loaded model parameters from {checkpoint_path}.")
119
+ model.backend.remove_weight_norm()
120
+ model = model.eval().to(device)
121
+
122
+ # load vq codebook
123
+ feat_codebook = torch.tensor(np.load(config["vq_codebook"], allow_pickle=True)).to(device) # (V, D)
124
+ if feat_codebook.ndim == 2:
125
+ feat_codebook = feat_codebook.unsqueeze(0)
126
+ feat_codebook_numgroups = feat_codebook.shape[0]
127
+ feat_codebook = torch.nn.ModuleList([torch.nn.Embedding.from_pretrained(feat_codebook[i], freeze=True) for i in range(feat_codebook_numgroups)])
128
+
129
+ # start generation
130
+ total_rtf = 0.0
131
+ feats_scp = kaldiio.load_scp(args.feats_scp)
132
+ prompt_wav_scp = dict()
133
+ with open(args.prompt_wav_scp, 'r') as fr:
134
+ for line in fr.readlines():
135
+ terms = line.strip().split()
136
+ if len(terms) != 2:
137
+ raise ValueError(f"Invalid line in prompt_wav_scp: {line.strip()}")
138
+ utt_id, audio_path = terms
139
+ prompt_wav_scp[utt_id] = audio_path
140
+
141
+ total_cnt = len(list(feats_scp.keys()))
142
+ logging.info(f"The number of features to be decoded = {total_cnt}.")
143
+
144
+ wavlm = WavLMExtractor(checkpoint=wavlm_path, device=device)
145
+
146
+ with torch.no_grad(), tqdm(feats_scp, desc="[decode]", total=total_cnt) as pbar:
147
+ for idx, utt_id in enumerate(pbar, 1):
148
+ c = feats_scp[utt_id]
149
+ # prompt = prompt_scp[utt_id]
150
+ prompt_path = prompt_wav_scp[utt_id]
151
+ prompt_audio, prompt_sr = load_audio(prompt_path)
152
+
153
+ if prompt_sr != 16000:
154
+ prompt_audio = librosa.resample(prompt_audio, orig_sr=prompt_sr, target_sr=16000)
155
+
156
+ c = torch.tensor(c).to(device) # (L, D)
157
+
158
+ prompt = wavlm.extract(prompt_audio).unsqueeze(0) # (1, L', D')
159
+
160
+ vqidx = c.long()
161
+ if config.get("repeat_input_tokens", False):
162
+ vqidx = vqidx.repeat_interleave(2, dim=0)
163
+ vqvec = torch.cat([feat_codebook[i](vqidx[:, i]) for i in range(feat_codebook_numgroups)], dim=-1).unsqueeze(0) # (1, L, D)
164
+
165
+ # generate
166
+ start = time.time()
167
+ y = model.inference(vqvec, prompt)[-1].view(-1)
168
+ rtf = (time.time() - start) / (len(y) / config["sampling_rate"])
169
+ pbar.set_postfix({"RTF": rtf})
170
+ total_rtf += rtf
171
+
172
+ tgt_dir = os.path.dirname(os.path.join(config["outdir"], f"{utt_id}.wav"))
173
+ os.makedirs(tgt_dir, exist_ok=True)
174
+ basename = os.path.basename(f"{utt_id}.wav")
175
+ # save as PCM 16 bit wav file
176
+ sf.write(
177
+ os.path.join(tgt_dir, basename),
178
+ y.cpu().numpy(),
179
+ config["sampling_rate"],
180
+ "PCM_16",
181
+ )
182
+
183
+ # report average RTF
184
+ logging.info(
185
+ f"Finished generation of {idx} utterances (RTF = {total_rtf / idx:.03f})."
186
+ )
187
+
188
+
189
+ if __name__ == "__main__":
190
+ main()
lscodec/bin/encode.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Tomoki Hayashi
5
+ # MIT License (https://opensource.org/licenses/MIT)
6
+
7
+ # MIT License
8
+ # Copyright (c) 2025 Yiwei Guo
9
+ # This file is based on ParallelWaveGAN (https://github.com/kan-bayashi/ParallelWaveGAN).
10
+ # Above is the original copyright notice.
11
+
12
+
13
+ """Encode with Trained LSCodec Encoder."""
14
+
15
+ import argparse
16
+ import logging
17
+ import os
18
+ import time
19
+
20
+ import numpy as np
21
+ import re
22
+ import librosa
23
+
24
+ import soundfile as sf
25
+ import torch
26
+ from pathlib import Path
27
+ import yaml
28
+
29
+ from tqdm import tqdm
30
+ import kaldiio
31
+
32
+ from lscodec.utils import load_model, load_audio
33
+ from kaldiio import WriteHelper
34
+
35
+
36
+ def main():
37
+ """Run decoding process."""
38
+ parser = argparse.ArgumentParser(
39
+ description="Encode features with trained LSCodec"
40
+ )
41
+ parser.add_argument(
42
+ "--wav-scp",
43
+ "--scp",
44
+ default=None,
45
+ type=str,
46
+ help="kaldi-style wav.scp file. "
47
+ "you need to specify wav-scp.",
48
+ )
49
+ parser.add_argument(
50
+ "--outdir",
51
+ type=str,
52
+ required=True,
53
+ help="directory to save generated speech.",
54
+ )
55
+ parser.add_argument(
56
+ "--pretrained-dir",
57
+ type=str,
58
+ default="pretrained/",
59
+ help="directory to save pretrained model. (default=None). It will need to contain lscodec_encoder.pt and encoder_config.yml",
60
+ )
61
+ parser.add_argument(
62
+ "--verbose",
63
+ type=int,
64
+ default=1,
65
+ help="logging level. higher is more logging. (default=1)",
66
+ )
67
+ args = parser.parse_args()
68
+
69
+ # set logger
70
+ if args.verbose > 1:
71
+ logging.basicConfig(
72
+ level=logging.DEBUG,
73
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
74
+ )
75
+ elif args.verbose > 0:
76
+ logging.basicConfig(
77
+ level=logging.INFO,
78
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
79
+ )
80
+ else:
81
+ logging.basicConfig(
82
+ level=logging.WARN,
83
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
84
+ )
85
+ logging.warning("Skip DEBUG/INFO messages")
86
+
87
+ # check directory existence
88
+ if not os.path.exists(args.outdir):
89
+ os.makedirs(args.outdir)
90
+
91
+ # load config
92
+ config_path = os.path.join(args.pretrained_dir, "encoder_config.yml")
93
+ checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_encoder.pt")
94
+ with open(config_path) as f:
95
+ config = yaml.load(f, Loader=yaml.Loader)
96
+ config.update(vars(args))
97
+ config['pretrain_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
98
+
99
+ # check arguments
100
+ if args.wav_scp is None:
101
+ raise ValueError("Please specify --wav-scp.")
102
+
103
+ utt2path = dict()
104
+ with open(args.wav_scp, 'r') as fr:
105
+ for line in fr.readlines():
106
+ terms = line.strip().split()
107
+ utt2path[terms[0]] = terms[1]
108
+ logging.info(f"The number of features to be decoded = {len(utt2path)}.")
109
+
110
+ # setup model
111
+ if torch.cuda.is_available():
112
+ device = torch.device("cuda")
113
+ logging.info("Using GPU.")
114
+ else:
115
+ device = torch.device("cpu")
116
+ logging.info("Using CPU.")
117
+ model = load_model(config, checkpoint_path)
118
+ logging.info(f"Loaded model parameters from {checkpoint_path}.")
119
+ model = model.eval().to(device)
120
+
121
+ # start generation
122
+ outdir = str(Path(args.outdir).absolute())
123
+ with torch.no_grad(), tqdm(utt2path.items(), desc="[encode]") as pbar, WriteHelper(f"ark,scp:{outdir}/feats.ark,{outdir}/feats.scp") as writer:
124
+ for i, (utt_id, audio_path) in enumerate(pbar, 1):
125
+ audio, sr = load_audio(audio_path)
126
+
127
+ if sr != config['feature_extractor_params']['fs']:
128
+ audio_for_extractor = librosa.resample(audio, orig_sr=sr, target_sr=config['feature_extractor_params']['fs'])
129
+ else:
130
+ audio_for_extractor = audio
131
+ audio_for_extractor = torch.tensor(audio_for_extractor).float().to(device) # (L)
132
+ audio_for_extractor = audio_for_extractor.unsqueeze(0).unsqueeze(0) # (1, 1, L)
133
+ means, _, embed_index = model.encode(audio_for_extractor)
134
+
135
+ # now the embed_index is [L, 1] token tensor
136
+ if embed_index is not None:
137
+ writer(utt_id, embed_index.float().cpu().numpy())
138
+ else:
139
+ writer(utt_id, means.squeeze(0).cpu().numpy())
140
+
141
+
142
+ if __name__ == "__main__":
143
+ main()
lscodec/bin/recon_with_prompt.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Tomoki Hayashi
5
+ # MIT License (https://opensource.org/licenses/MIT)
6
+
7
+ # MIT License
8
+ # Copyright (c) 2025 Yiwei Guo
9
+ # This file is based on ParallelWaveGAN (https://github.com/kan-bayashi/ParallelWaveGAN).
10
+ # Above is the original copyright notice.
11
+
12
+
13
+ """Decode with trained Parallel WaveGAN Generator."""
14
+
15
+ import argparse
16
+ import logging
17
+ from kaldiio import WriteHelper
18
+ from pathlib import Path
19
+ import os
20
+ import time
21
+ import re
22
+
23
+ import numpy as np
24
+ import librosa
25
+
26
+ import soundfile as sf
27
+ import torch
28
+ import yaml
29
+
30
+ from tqdm import tqdm
31
+ import kaldiio
32
+ from lscodec.ssl_models.wavlm_extractor import Extractor as WavLMExtractor
33
+ from lscodec.utils import load_model, load_vocoder, load_audio
34
+
35
+ def main():
36
+ """Run decoding process."""
37
+ parser = argparse.ArgumentParser(
38
+ description="Decode dumped features with trained VQVAE "
39
+ )
40
+ parser.add_argument(
41
+ "--wav-scp",
42
+ "--scp",
43
+ default=None,
44
+ type=str,
45
+ help="kaldi-style wav.scp file. "
46
+ "you need to specify either wav-scp or dumpdir.",
47
+ )
48
+ parser.add_argument(
49
+ "--prompt-wav-scp",
50
+ default=None,
51
+ type=str,
52
+ help="kaldi-style feats.scp file. "
53
+ )
54
+ parser.add_argument(
55
+ "--outdir",
56
+ type=str,
57
+ required=True,
58
+ help="directory to save generated speech.",
59
+ )
60
+ # parser.add_argument(
61
+ # "--encoder-checkpoint",
62
+ # type=str,
63
+ # default="pretrained/lscodec_encoder.pt",
64
+ # help="checkpoint file to be loaded.",
65
+ # )
66
+ # parser.add_argument(
67
+ # "--encoder-config",
68
+ # default='pretrained/encoder_config.yml',
69
+ # type=str,
70
+ # help="yaml format configuration file. if not explicitly provided, "
71
+ # "it will be searched in the checkpoint directory. (default=None)",
72
+ # )
73
+ # parser.add_argument(
74
+ # "--vocoder-checkpoint",
75
+ # type=str,
76
+ # default="pretrained/lscodec_vocoder.pt",
77
+ # help="checkpoint file to be loaded.",
78
+ # )
79
+ # parser.add_argument(
80
+ # "--vocoder-config",
81
+ # default='pretrained/vocoder_config.yml',
82
+ # type=str,
83
+ # help="yaml format configuration file. if not explicitly provided, "
84
+ # "it will be searched in the checkpoint directory. (default=None)",
85
+ # )
86
+ # parser.add_argument(
87
+ # "--wavlm-path",
88
+ # type=str,
89
+ # default="pretrained/WavLM-Large.pt",
90
+ # help="path to WavLM checkpoint.",
91
+ # )
92
+ parser.add_argument(
93
+ "--pretrained-dir",
94
+ type=str,
95
+ default="pretrained/",
96
+ help="directory to save pretrained model. (default=None). It will need to contain WavLM-Large.pt, lscodec_encoder.pt, lscodec_vocoder.pt, encoder_config.yml, vocoder_config.yml and codebook.npy",
97
+ )
98
+ parser.add_argument(
99
+ "--verbose",
100
+ type=int,
101
+ default=1,
102
+ help="logging level. higher is more logging. (default=1)",
103
+ )
104
+ args = parser.parse_args()
105
+
106
+ # set logger
107
+ if args.verbose > 1:
108
+ logging.basicConfig(
109
+ level=logging.DEBUG,
110
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
111
+ )
112
+ elif args.verbose > 0:
113
+ logging.basicConfig(
114
+ level=logging.INFO,
115
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
116
+ )
117
+ else:
118
+ logging.basicConfig(
119
+ level=logging.WARN,
120
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
121
+ )
122
+ logging.warning("Skip DEBUG/INFO messages")
123
+
124
+ # check directory existence
125
+ if not os.path.exists(args.outdir):
126
+ os.makedirs(args.outdir)
127
+
128
+ # load encoder config
129
+ encoder_config_path = os.path.join(args.pretrained_dir, "encoder_config.yml")
130
+ encoder_checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_encoder.pt")
131
+ with open(encoder_config_path) as f:
132
+ encoder_config = yaml.load(f, Loader=yaml.Loader)
133
+ encoder_config.update(vars(args))
134
+ encoder_config['pretrain_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
135
+
136
+ # load vocoder config
137
+ vocoder_config_path = os.path.join(args.pretrained_dir, "vocoder_config.yml")
138
+ vocoder_checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_vocoder.pt")
139
+ with open(vocoder_config_path) as f:
140
+ vocoder_config = yaml.load(f, Loader=yaml.Loader)
141
+ vocoder_config.update(vars(args))
142
+ vocoder_config['vq_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
143
+
144
+ # check arguments
145
+ if args.wav_scp is None or args.prompt_wav_scp is None:
146
+ raise ValueError("Please specify --wav-scp and --prompt-wav-scp.")
147
+
148
+ utt2path = dict()
149
+ with open(args.wav_scp, 'r') as fr:
150
+ for line in fr.readlines():
151
+ terms = line.strip().split()
152
+ if len(terms) != 2:
153
+ raise ValueError(f"Invalid line in wav_scp: {line.strip()}")
154
+ utt2path[terms[0]] = terms[1]
155
+ prompt_scp = dict()
156
+ with open(args.prompt_wav_scp, 'r') as fr:
157
+ for line in fr.readlines():
158
+ terms = line.strip().split()
159
+ if len(terms) != 2:
160
+ raise ValueError(f"Invalid line in prompt_wav_scp: {line.strip()}")
161
+ utt_id, audio_path = terms
162
+ prompt_scp[utt_id] = audio_path
163
+
164
+ logging.info(f"The number of features to be decoded = {len(utt2path)}.")
165
+
166
+ # setup model
167
+ if torch.cuda.is_available():
168
+ device = torch.device("cuda")
169
+ logging.info("Using GPU.")
170
+ else:
171
+ device = torch.device("cpu")
172
+ logging.info("Using CPU.")
173
+ model = load_model(encoder_config, encoder_checkpoint_path)
174
+ logging.info(f"Loaded encoder parameters from {encoder_checkpoint_path}.")
175
+ model = model.eval().to(device)
176
+
177
+ vocoder = load_vocoder(vocoder_config, vocoder_checkpoint_path)
178
+ logging.info(f"Loaded vocoder parameters from {vocoder_checkpoint_path}.")
179
+ vocoder = vocoder.eval().to(device)
180
+ # load vq codebook
181
+ feat_codebook = torch.tensor(np.load(vocoder_config["vq_codebook"], allow_pickle=True)).to(device) # (V, D)
182
+ if feat_codebook.ndim == 2:
183
+ feat_codebook = feat_codebook.unsqueeze(0)
184
+ feat_codebook_numgroups = feat_codebook.shape[0]
185
+ feat_codebook = torch.nn.ModuleList([torch.nn.Embedding.from_pretrained(feat_codebook[i], freeze=True) for i in range(feat_codebook_numgroups)])
186
+
187
+ wavlm_path = os.path.join(args.pretrained_dir, "WavLM-Large.pt")
188
+ wavlm = WavLMExtractor(checkpoint=wavlm_path, device=device)
189
+
190
+ # start generation
191
+ with torch.no_grad(), tqdm(utt2path.items(), desc="[prompted recon]") as pbar:
192
+ for idx, (utt_id, audio_path) in enumerate(pbar, 1):
193
+ prompt_path = prompt_scp[utt_id]
194
+ audio, sr = load_audio(audio_path)
195
+ prompt_audio, prompt_sr = load_audio(prompt_path)
196
+
197
+ if sr != encoder_config['feature_extractor_params']['fs']:
198
+ audio = librosa.resample(audio, orig_sr=sr, target_sr=encoder_config['feature_extractor_params']['fs'])
199
+
200
+ if prompt_sr != 16000:
201
+ prompt_audio = librosa.resample(prompt_audio, orig_sr=prompt_sr, target_sr=16000)
202
+
203
+ audio = torch.tensor(audio).float().to(device) # (L)
204
+ audio = audio.unsqueeze(0).unsqueeze(0) # (1, 1, L)
205
+
206
+ prompt = wavlm.extract(prompt_audio)
207
+ prompt = prompt.float().unsqueeze(0).to(device) # [1, L, D]
208
+
209
+ # generate
210
+ _, _, embed_index = model.encode(audio)
211
+ if vocoder_config.get("repeat_input_tokens", False):
212
+ # in LSCodec-25Hz, we still trained the vocoder on 50Hz input, so repeat_interleave is needed.
213
+ embed_index = embed_index.repeat_interleave(2, dim=0)
214
+ vqvec = torch.cat([feat_codebook[i](embed_index[:, i]) for i in range(feat_codebook_numgroups)], dim=-1).unsqueeze(0) # (1, L, D)
215
+ y = vocoder.inference(vqvec, prompt)[-1].view(-1)
216
+
217
+ tgt_dir = os.path.dirname(os.path.join(args.outdir, f"{utt_id}.wav"))
218
+ os.makedirs(tgt_dir, exist_ok=True)
219
+ basename = os.path.basename(f"{utt_id}.wav")
220
+ # save as PCM 16 bit wav file
221
+ sf.write(
222
+ os.path.join(tgt_dir, basename),
223
+ y.cpu().numpy(),
224
+ vocoder_config["sampling_rate"],
225
+ "PCM_16",
226
+ )
227
+
228
+ # report average RTF
229
+ logging.info(
230
+ f"Finished generation of {idx} utterances."
231
+ )
232
+
233
+
234
+ if __name__ == "__main__":
235
+ main()
lscodec/bin/train.py ADDED
@@ -0,0 +1,994 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Tomoki Hayashi
5
+ # MIT License (https://opensource.org/licenses/MIT)
6
+
7
+ # Portions of this code are a modification of the ParallelWaveGAN project.
8
+ # The original source code can be found at:
9
+ # https://github.com/kan-bayashi/ParallelWaveGAN/blob/master/parallel_wavegan/bin/train.py
10
+ #
11
+ # Copyright (c) 2025 Yiwei Guo
12
+ # Licensed under the MIT license.
13
+
14
+ import argparse
15
+ import logging
16
+ import os
17
+ import sys
18
+ import random
19
+
20
+ from collections import defaultdict
21
+
22
+ import matplotlib
23
+ import numpy as np
24
+ import soundfile as sf
25
+ import torch
26
+ import torch.nn.functional as F
27
+ import yaml
28
+ import torch.multiprocessing as mp
29
+ from tensorboardX import SummaryWriter
30
+ from torch.utils.data import DataLoader
31
+ from tqdm import tqdm
32
+
33
+ import lscodec
34
+ import lscodec.models
35
+ from lscodec.models.quantization.core_vq import GroupVectorQuantization
36
+ import lscodec.optimizers
37
+ from torch.utils.data.distributed import DistributedSampler
38
+
39
+ from lscodec.datasets.scp_dataset import AudioMelSCPDataset
40
+ from lscodec.utils.espnet_utils import pad_list, make_non_pad_mask
41
+ from lscodec.datasets.perturbs import Perturbs
42
+
43
+ # set to avoid matplotlib error in CLI environment
44
+ matplotlib.use("Agg")
45
+
46
+
47
+ def KL_loss(means, logvars, mask):
48
+ means = means.masked_select(mask)
49
+ logvars = logvars.masked_select(mask)
50
+ logvars = torch.clamp(logvars, -3, 3) # NOTE: added to ensure stability
51
+ loss = -0.5*logvars + 0.5*(logvars.exp() + means.pow(2)) - 0.5
52
+ # if torch.isnan(loss).any():
53
+ # logging.info("detected nan in kl_loss")
54
+ # raise ValueError()
55
+ # loss = torch.nan_to_num(loss, nan=10)
56
+ return loss.mean()
57
+
58
+
59
+ class Trainer(object):
60
+ """Customized trainer module for Parallel WaveGAN training."""
61
+
62
+ def __init__(
63
+ self,
64
+ steps,
65
+ epochs,
66
+ data_loader,
67
+ sampler,
68
+ model,
69
+ optimizer,
70
+ scheduler,
71
+ config,
72
+ device=torch.device("cpu"),
73
+ ):
74
+ """Initialize trainer.
75
+
76
+ Args:
77
+ steps (int): Initial global steps.
78
+ epochs (int): Initial global epochs.
79
+ data_loader (dict): Dict of data loaders. It must contain "train" and "dev" loaders.
80
+ model (dict): Dict of models. It must contain "generator" model.
81
+ optimizer (dict): Dict of optimizers. It must contain "generator" and optimizer.
82
+ scheduler (dict): Dict of schedulers. It must contain "generator" and schedulers.
83
+ config (dict): Config dict loaded from yaml format configuration file.
84
+ device (torch.deive): Pytorch device instance.
85
+
86
+ """
87
+ self.steps = steps
88
+ self.epochs = epochs
89
+ self.data_loader = data_loader
90
+ self.sampler = sampler
91
+ self.model = model
92
+ self.optimizer = optimizer
93
+ self.scheduler = scheduler
94
+ self.config = config
95
+ self.device = device
96
+ self.writer = SummaryWriter(config["outdir"])
97
+ self.finish_train = False
98
+ self.total_train_loss = defaultdict(float)
99
+ self.total_eval_loss = defaultdict(float)
100
+
101
+ def run(self):
102
+ """Run training."""
103
+ self.tqdm = tqdm(
104
+ initial=self.steps, total=self.config["train_max_steps"], desc="[train]"
105
+ )
106
+ while True:
107
+ # train one epoch
108
+ self._train_epoch()
109
+
110
+ # check whether training is finished
111
+ if self.finish_train:
112
+ break
113
+
114
+ self.tqdm.close()
115
+ logging.info("Finished training.")
116
+
117
+ def save_checkpoint(self, checkpoint_path):
118
+ """Save checkpoint.
119
+
120
+ Args:
121
+ checkpoint_path (str): Checkpoint path to be saved.
122
+
123
+ """
124
+ state_dict = {
125
+ "optimizer": {
126
+ "generator": self.optimizer["generator"].state_dict(),
127
+ },
128
+ "scheduler": {
129
+ "generator": self.scheduler["generator"].state_dict(),
130
+ },
131
+ "steps": self.steps,
132
+ "epochs": self.epochs,
133
+ }
134
+ if self.config["distributed"]:
135
+ state_dict["model"] = {
136
+ "generator": self.model["generator"].module.state_dict(),
137
+ }
138
+ else:
139
+ state_dict["model"] = {
140
+ "generator": self.model["generator"].state_dict(),
141
+ }
142
+
143
+ if not os.path.exists(os.path.dirname(checkpoint_path)):
144
+ os.makedirs(os.path.dirname(checkpoint_path))
145
+ torch.save(state_dict, checkpoint_path)
146
+
147
+ def load_checkpoint(self, checkpoint_path, load_only_params=False):
148
+ """Load checkpoint.
149
+
150
+ Args:
151
+ checkpoint_path (str): Checkpoint path to be loaded.
152
+ load_only_params (bool): Whether to load only model parameters.
153
+
154
+ """
155
+ state_dict = torch.load(checkpoint_path, map_location="cpu")
156
+ if self.config["distributed"]:
157
+ self.model["generator"].module.load_state_dict(
158
+ state_dict["model"]["generator"]
159
+ )
160
+ else:
161
+ self.model["generator"].load_state_dict(state_dict["model"]["generator"])
162
+ if not load_only_params:
163
+ self.steps = state_dict["steps"]
164
+ self.epochs = state_dict["epochs"]
165
+ self.optimizer["generator"].load_state_dict(
166
+ state_dict["optimizer"]["generator"]
167
+ )
168
+ self.scheduler["generator"].load_state_dict(
169
+ state_dict["scheduler"]["generator"]
170
+ )
171
+
172
+ def load_vae_params(self, checkpoint_path):
173
+ state_dict = torch.load(checkpoint_path, map_location="cpu")
174
+ generator = state_dict['model']['generator']
175
+ frontend_state_dict = {}
176
+ backend_state_dict = {}
177
+ for key in generator.keys():
178
+ if key.startswith("frontend"):
179
+ frontend_state_dict[".".join(key.split(".")[1:])] = generator[key]
180
+ elif key.startswith("backend"):
181
+ backend_state_dict[".".join(key.split(".")[1:])] = generator[key]
182
+ else:
183
+ raise NotImplementedError(f"Unknown key: {key}")
184
+ if self.config["distributed"]:
185
+ self.model['generator'].module.frontend.load_state_dict(frontend_state_dict)
186
+ self.model['generator'].module.backend.load_state_dict(backend_state_dict)
187
+ else:
188
+ self.model['generator'].frontend.load_state_dict(frontend_state_dict)
189
+ self.model['generator'].backend.load_state_dict(backend_state_dict)
190
+
191
+ def open_codebook_expire(self):
192
+ if self.config['distributed']:
193
+ for group_i in range(len(self.model['generator'].module.quantizer.groups)):
194
+ self.model['generator'].module.quantizer.groups[group_i]._codebook.perform_expire = True
195
+ else:
196
+ for group_i in range(len(self.model['generator'].quantizer.groups)):
197
+ self.model['generator'].quantizer.groups[group_i]._codebook.perform_expire = True
198
+
199
+ def open_codebook_ema(self):
200
+ if self.config['distributed']:
201
+ for group_i in range(len(self.model['generator'].module.quantizer.groups)):
202
+ self.model['generator'].module.quantizer.groups[group_i]._codebook.perform_ema_update = True
203
+ else:
204
+ for group_i in range(len(self.model['generator'].quantizer.groups)):
205
+ self.model['generator'].quantizer.groups[group_i]._codebook.perform_ema_update = True
206
+
207
+ def _train_step(self, batch):
208
+ """Train model one step."""
209
+ if self.config['have_quantizer'] and self.config['quantizer_class'] == "KMeans":
210
+ if self.steps > self.config['quantizer_params']['perform_expire_after_steps']:
211
+ self.open_codebook_expire()
212
+ if self.steps > self.config['quantizer_params']['perform_ema_after_steps']:
213
+ self.open_codebook_ema()
214
+ if self.config['have_quantizer'] and self.config['quantizer_class'] == "Gumbel":
215
+ self.model['generator'].module.quantizer.set_num_updates(self.steps)
216
+
217
+ # parse batch
218
+ aux, target, idx, prompt, y_for_extractor, xlens, prompt_lens = batch
219
+ target = target.to(self.device)
220
+ prompt = prompt.to(self.device)
221
+ idx = idx.to(self.device)
222
+ y_for_extractor = y_for_extractor.unsqueeze(-2).to(self.device) # (B, 1, T')
223
+
224
+ # build mask
225
+ mask = make_non_pad_mask(xlens).to(self.device) # (B, L) True for valid values
226
+ prompt_mask = make_non_pad_mask(prompt_lens).to(self.device) # (B, L_prompt)
227
+
228
+ #######################
229
+ # Generator #
230
+ #######################
231
+ if self.config['distributed']:
232
+ tolerance = self.model['generator'].module.length_mismatch_tolerance
233
+ else:
234
+ tolerance = self.model['generator'].length_mismatch_tolerance
235
+ target_, idx_logits_, _, commitment_loss, mask, means, logvars = self.model["generator"](y_for_extractor, prompt, mask, prompt_mask)
236
+ # (B, L, 80)
237
+
238
+ if target.shape[1] != mask.shape[-1]:
239
+ if abs(target.shape[1] - mask.shape[-1]) <= tolerance:
240
+ min_len = min(target.shape[1], mask.shape[-1])
241
+ target = target[:, :min_len, :]
242
+ mask = mask[..., :min_len]
243
+ target_ = target_[:, :min_len, :]
244
+ idx = idx[:, :min_len, :]
245
+ idx_logits_ = idx_logits_[:, :min_len, :]
246
+
247
+ # initialize
248
+ gen_loss = 0.0
249
+ # frontend target prediction loss
250
+ frontend_target_pred_loss = F.l1_loss(torch.masked_select(target, mask.unsqueeze(-1)),
251
+ torch.masked_select(target_, mask.unsqueeze(-1)))
252
+ self.total_train_loss["train/frontend_target_pred_loss"] += frontend_target_pred_loss.item()
253
+ gen_loss += self.config["lambda_frontend_target_prediction"] * frontend_target_pred_loss
254
+
255
+ idx_criterion = torch.nn.CrossEntropyLoss()
256
+ V = idx_logits_.shape[-1]
257
+ frontend_idx_pred_loss = idx_criterion(torch.masked_select(idx_logits_, mask.unsqueeze(-1)).view(-1, V),
258
+ torch.masked_select(idx.squeeze(-1), mask))
259
+ self.total_train_loss["train/frontend_idx_pred_loss"] += frontend_idx_pred_loss.item()
260
+ gen_loss += self.config["lambda_frontend_idx_prediction"] * frontend_idx_pred_loss
261
+
262
+ if commitment_loss is None:
263
+ if self.config['quantizer_class'] != "Gumbel": # In this case, no additional loss.
264
+ # VQVAE KL loss
265
+ kl_loss = KL_loss(means, logvars, mask.unsqueeze(-1))
266
+ gen_loss += self.config['lambda_kl_loss'] * kl_loss
267
+ self.total_train_loss['train/kl_loss'] += kl_loss.item()
268
+ else:
269
+ # VQ loss (if kmeans)
270
+ if self.config['quantizer_class'] == "KMeans":
271
+ gen_loss += self.config['lambda_kmeans_loss'] * commitment_loss
272
+ self.total_train_loss['train/kmeans_loss'] += commitment_loss.item()
273
+
274
+ self.total_train_loss["train/generator_loss"] += gen_loss.item()
275
+
276
+ # update generator
277
+ self.optimizer["generator"].zero_grad()
278
+ # gen_loss = torch.nan_to_num(gen_loss, nan=100) # NOTE: will this ensure stability?
279
+ if torch.isnan(gen_loss):
280
+ logging.info(f"Find NAN in loss. Refusing to update.")
281
+ gen_loss = 0.
282
+ for p in self.model['generator'].parameters():
283
+ gen_loss = gen_loss + 0.0 * p.sum()
284
+ # gen_loss = torch.sum([0.0*p.sum() for p in self.model['generator'].parameters()]) # fake loss. Gradient=0
285
+
286
+ gen_loss.backward()
287
+ if self.config["generator_grad_norm"] > 0:
288
+ torch.nn.utils.clip_grad_norm_(
289
+ self.model["generator"].parameters(),
290
+ self.config["generator_grad_norm"],
291
+ )
292
+ self.optimizer["generator"].step()
293
+ self.scheduler["generator"].step()
294
+ # update counts
295
+ self.steps += 1
296
+ self.tqdm.update(1)
297
+ self._check_train_finish()
298
+
299
+ def _train_epoch(self):
300
+ """Train model one epoch."""
301
+ for train_steps_per_epoch, batch in enumerate(self.data_loader["train"], 1):
302
+ # train one step
303
+ self._train_step(batch)
304
+
305
+ # check interval
306
+ if self.config["rank"] == 0:
307
+ self._check_log_interval()
308
+ self._check_eval_interval()
309
+ self._check_save_interval()
310
+
311
+ # check whether training is finished
312
+ if self.finish_train:
313
+ return
314
+
315
+ # update
316
+ self.epochs += 1
317
+ self.train_steps_per_epoch = train_steps_per_epoch
318
+ logging.info(
319
+ f"(Steps: {self.steps}) Finished {self.epochs} epoch training "
320
+ f"({self.train_steps_per_epoch} steps per epoch)."
321
+ )
322
+
323
+ # needed for shuffle in distributed training
324
+ if self.config["distributed"]:
325
+ self.sampler["train"].set_epoch(self.epochs)
326
+
327
+ @torch.no_grad()
328
+ def _eval_step(self, batch):
329
+ """Evaluate model one step."""
330
+ # parse batch
331
+ aux, target, idx, prompt, y_for_extractor, xlens, prompt_lens = batch
332
+ target = target.to(self.device)
333
+ prompt = prompt.to(self.device)
334
+ idx = idx.to(self.device)
335
+ y_for_extractor = y_for_extractor.unsqueeze(-2).to(self.device) # (B, 1, T')
336
+
337
+ # build mask
338
+ mask = make_non_pad_mask(xlens).to(self.device) # (B, L)
339
+ prompt_mask = make_non_pad_mask(prompt_lens).to(self.device) # (B, L_prompt)
340
+
341
+ #######################
342
+ # Generator #
343
+ #######################
344
+ if self.config['distributed']:
345
+ tolerance = self.model['generator'].module.length_mismatch_tolerance
346
+ else:
347
+ tolerance = self.model['generator'].length_mismatch_tolerance
348
+ target_, idx_logits_, _, commitment_loss, mask, means, logvars = self.model["generator"](y_for_extractor, prompt, mask, prompt_mask) # (B, L, 80), (B, C, T)
349
+ if target.shape[1] != mask.shape[-1]:
350
+ if abs(target.shape[1] - mask.shape[-1]) <= tolerance:
351
+ min_len = min(target.shape[1], mask.shape[-1])
352
+ target = target[:, :min_len, :]
353
+ mask = mask[..., :min_len]
354
+ target_ = target_[:, :min_len, :]
355
+ idx = idx[:, :min_len, :]
356
+ idx_logits_ = idx_logits_[:, :min_len, :]
357
+
358
+ # frontend target prediction loss
359
+ frontend_target_pred_loss = F.l1_loss(torch.masked_select(target, mask.unsqueeze(-1)),
360
+ torch.masked_select(target_, mask.unsqueeze(-1)))
361
+ self.total_eval_loss["eval/frontend_target_pred_loss"] += frontend_target_pred_loss.item()
362
+
363
+ idx_criterion = torch.nn.CrossEntropyLoss()
364
+ V = idx_logits_.shape[-1]
365
+ frontend_idx_pred_loss = idx_criterion(torch.masked_select(idx_logits_, mask.unsqueeze(-1)).view(-1, V),
366
+ torch.masked_select(idx.squeeze(-1), mask))
367
+ self.total_eval_loss["eval/frontend_idx_pred_loss"] += frontend_idx_pred_loss.item()
368
+
369
+ if commitment_loss is None:
370
+ if self.config['quantizer_class'] != "Gumbel": # In this case, no additional loss.
371
+ # VAE KL loss
372
+ kl_loss = KL_loss(means, logvars, mask.unsqueeze(-1))
373
+ self.total_eval_loss['eval/kl_loss'] += kl_loss.item()
374
+ else:
375
+ # VQ loss (if kmeans)
376
+ if self.config['quantizer_class'] == "KMeans":
377
+ self.total_eval_loss['eval/kmeans_loss'] += commitment_loss.item()
378
+
379
+ def _eval_epoch(self):
380
+ """Evaluate model one epoch."""
381
+ logging.info(f"(Steps: {self.steps}) Start evaluation.")
382
+ # change mode
383
+ for key in self.model.keys():
384
+ self.model[key].eval()
385
+
386
+ # calculate loss for each batch
387
+ for eval_steps_per_epoch, batch in enumerate(
388
+ tqdm(self.data_loader["dev"], desc="[eval]"), 1
389
+ ):
390
+ # eval one step
391
+ self._eval_step(batch)
392
+
393
+ logging.info(
394
+ f"(Steps: {self.steps}) Finished evaluation "
395
+ f"({eval_steps_per_epoch} steps per epoch)."
396
+ )
397
+
398
+ # average loss
399
+ for key in self.total_eval_loss.keys():
400
+ self.total_eval_loss[key] /= eval_steps_per_epoch
401
+ logging.info(
402
+ f"(Steps: {self.steps}) {key} = {self.total_eval_loss[key]:.4f}."
403
+ )
404
+
405
+ # record
406
+ self._write_to_tensorboard(self.total_eval_loss)
407
+
408
+ # reset
409
+ self.total_eval_loss = defaultdict(float)
410
+
411
+ # restore mode
412
+ for key in self.model.keys():
413
+ self.model[key].train()
414
+
415
+ def _write_to_tensorboard(self, loss):
416
+ """Write to tensorboard."""
417
+ for key, value in loss.items():
418
+ self.writer.add_scalar(key, value, self.steps)
419
+
420
+ def _check_save_interval(self):
421
+ if self.steps % self.config["save_interval_steps"] == 0:
422
+ self.save_checkpoint(
423
+ os.path.join(self.config["outdir"], f"checkpoint-{self.steps}steps.pkl")
424
+ )
425
+ logging.info(f"Successfully saved checkpoint @ {self.steps} steps.")
426
+
427
+ def _check_eval_interval(self):
428
+ if self.steps % self.config["eval_interval_steps"] == 0:
429
+ self._eval_epoch()
430
+
431
+ def _check_log_interval(self):
432
+ if self.steps % self.config["log_interval_steps"] == 0:
433
+ for key in self.total_train_loss.keys():
434
+ self.total_train_loss[key] /= self.config["log_interval_steps"]
435
+ logging.info(
436
+ f"(Steps: {self.steps}) {key} = {self.total_train_loss[key]:.4f}."
437
+ )
438
+ if self.config['have_quantizer'] and self.config['quantizer_class'] == "Gumbel":
439
+ logging.info(f"(Steps: {self.steps}) gumbel temperature = {self.model['generator'].module.quantizer.curr_temp}")
440
+ self._write_to_tensorboard(self.total_train_loss)
441
+
442
+ # reset
443
+ self.total_train_loss = defaultdict(float)
444
+
445
+ def _check_train_finish(self):
446
+ if self.steps >= self.config["train_max_steps"]:
447
+ self.finish_train = True
448
+
449
+
450
+ class Collator(object):
451
+ """Customized collator for Pytorch DataLoader in training."""
452
+
453
+ def __init__(
454
+ self,
455
+ hop_size_for_extractor=256,
456
+ win_length_for_extractor=1024,
457
+ sampling_rate=16000,
458
+ n_mel=80,
459
+ force_from_start=False,
460
+ ):
461
+ """Initialize customized collator for PyTorch DataLoader.
462
+
463
+ Args:
464
+ hop_size_for_extractor (int): Hop size of features, in sampling points.
465
+ win_length_for_extractor (int): window length of features.
466
+ sampling_rate (int): sampling rate of waveform data
467
+ n_mel (int): number of mel spectrogram dimensions
468
+ """
469
+ self.hop_size_for_extractor = hop_size_for_extractor
470
+ self.win_length_for_extractor = win_length_for_extractor
471
+ self.sampling_rate = sampling_rate
472
+ self.n_mel = n_mel
473
+ self.force_from_start = force_from_start
474
+
475
+ def construct_prompt(self, mel_lens):
476
+ prompt_lens = [random.randint(int(l / 3), int(l / 2)) for l in mel_lens]
477
+ prompt_starts = []
478
+ is_from_start = []
479
+ for ml, pl in zip(mel_lens, prompt_lens):
480
+ random_number = random.random()
481
+ if self.force_from_start:
482
+ random_number = 1
483
+ if random_number > 0.5:
484
+ # from start
485
+ prompt_start = random.randint(0, 1 * self.sampling_rate//self.hop_size_for_extractor)
486
+ is_from_start.append(True)
487
+ else:
488
+ # from ending
489
+ prompt_start = random.randint((ml - 1 * self.sampling_rate//self.hop_size_for_extractor), ml) - pl
490
+ is_from_start.append(False)
491
+ prompt_starts.append(prompt_start)
492
+ return prompt_lens, prompt_starts, is_from_start
493
+
494
+ def __call__(self, batch):
495
+ """Convert into batch tensors.
496
+
497
+ Args:
498
+ batch (list): list of tuple of the pair of audio and features.
499
+
500
+ This collator will automatically determine the prompt segment (acoustic context) for each utterance.
501
+ The prompt is cut off from the current utterance, ranging from 2 to 3 seconds.
502
+ The starting point of the prompt lies between the first 0-1 second range.
503
+ Then, it parses the concatenated features into (3 dim auxiliary features, 2 dim VQ features, and 80 dim mel spectrograms)
504
+
505
+ Returns:
506
+ Tensor: Gaussian noise batch (B, 1, T).
507
+ Tensor: Auxiliary feature batch (B, C, T'), where
508
+ T = T' * hop_size.
509
+ Tensor: Target signal batch (B, 1, T).
510
+
511
+ """
512
+ batch = batch[0]
513
+
514
+ # check length
515
+ batch = [
516
+ self._adjust_length(*b) for b in batch
517
+ ]
518
+ ys_for_extractor, mels, auxs, targets, idxs = ([b[0] for b in batch], [b[1] for b in batch], [b[2] for b in batch], [b[3] for b in batch], [b[4] for b in batch])
519
+
520
+ batch_size = len(ys_for_extractor)
521
+ prompt_lengths, prompt_starts, is_from_starts = self.construct_prompt([len(m) for m in mels])
522
+
523
+ c_lengths = []
524
+
525
+ prompts = torch.zeros(batch_size, max(prompt_lengths), self.n_mel)
526
+ for i in range(batch_size):
527
+ # data_offset = prompt_offsets[i] + prompt_lengths[i]
528
+ prompts[i, :prompt_lengths[i]] = torch.tensor(mels[i][prompt_starts[i]:prompt_starts[i]+prompt_lengths[i], :])
529
+ if is_from_starts[i]:
530
+ targets[i] = targets[i][prompt_starts[i]+prompt_lengths[i]:]
531
+ idxs[i] = idxs[i][prompt_starts[i]+prompt_lengths[i]:]
532
+ auxs[i] = auxs[i][prompt_starts[i]+prompt_lengths[i]:]
533
+ ys_for_extractor[i] = ys_for_extractor[i][(prompt_starts[i]+prompt_lengths[i])*self.hop_size_for_extractor:]
534
+ else:
535
+ targets[i] = targets[i][:prompt_starts[i]]
536
+ idxs[i] = idxs[i][:prompt_starts[i]]
537
+ auxs[i] = auxs[i][:prompt_starts[i]]
538
+ ys_for_extractor[i] = ys_for_extractor[i][:prompt_starts[i]*self.hop_size_for_extractor]
539
+ c_lengths.append(len(targets[i]))
540
+ # NOTE: PAD_VALUES is set to 999 instead of 0, so that the code vectors to be quantized are not affected.
541
+ targets = pad_list([torch.tensor(c) for c in targets], pad_value=0) # (B, L, 80)
542
+ idxs = pad_list([torch.tensor(c) for c in idxs], pad_value=0) # (B, L, 80)
543
+ idxs = idxs.long()
544
+ auxs = pad_list([torch.tensor(c) for c in auxs], pad_value=0) # (B, L, 3)
545
+ ys_for_extractor = pad_list([torch.tensor(y, dtype=torch.float) for y in ys_for_extractor],
546
+ pad_value=0)[:, :targets.size(1) * self.hop_size_for_extractor]
547
+ assert targets.size(1) == auxs.size(1) == idxs.size(1)
548
+ assert ys_for_extractor.size(1) == targets.size(1) * self.hop_size_for_extractor == auxs.size(1) * self.hop_size_for_extractor
549
+
550
+ return auxs, targets, idxs, prompts, ys_for_extractor, c_lengths, prompt_lengths
551
+
552
+ def _adjust_length(self, x, c, *args):
553
+ """Adjust the audio and feature lengths.
554
+
555
+ Note:
556
+ Basically we assume that the length of x and c are adjusted
557
+ through preprocessing stage, but if we use other library processed
558
+ features, this process will be needed.
559
+
560
+ """
561
+ if len(x) > len(c) * self.hop_size_for_extractor:
562
+ x = x[(self.win_length_for_extractor - self.hop_size_for_extractor) // 2:]
563
+ x = x[:len(c) * self.hop_size_for_extractor]
564
+
565
+ # check the legnth is valid
566
+ assert len(x) == len(c) * self.hop_size_for_extractor, f"{len(x)}, {len(c)}, {self.hop_size_for_extractor}"
567
+
568
+ return x, c, *args
569
+
570
+
571
+ def main(rank, n_gpus):
572
+ """Run training process."""
573
+ parser = argparse.ArgumentParser(
574
+ description="Train Parallel WaveGAN (See detail in ctx_vec2wav/bin/train.py)."
575
+ )
576
+ parser.add_argument(
577
+ "--train-wav-scp",
578
+ default=None,
579
+ type=str,
580
+ help="kaldi-style wav.scp file for training. "
581
+ )
582
+ parser.add_argument(
583
+ "--train-idx-scp",
584
+ default=None,
585
+ type=str,
586
+ help="kaldi-style feats.scp file for training. "
587
+ )
588
+ parser.add_argument(
589
+ "--train-prompt-scp",
590
+ default=None,
591
+ type=str,
592
+ help="kaldi-style feats.scp file for training. "
593
+ )
594
+ parser.add_argument(
595
+ "--train-target-scp",
596
+ default=None,
597
+ type=str,
598
+ help="kaldi-style feats.scp file for training. "
599
+ )
600
+ parser.add_argument(
601
+ "--train-aux-scp",
602
+ default=None,
603
+ type=str,
604
+ help="kaldi-style feats.scp file for training. "
605
+ )
606
+ parser.add_argument(
607
+ "--train-segments",
608
+ default=None,
609
+ type=str,
610
+ help="kaldi-style segments file for training.",
611
+ )
612
+ # parser.add_argument(
613
+ # "--train-xvector-scp",
614
+ # default=None,
615
+ # type=str,
616
+ # help="kaldi-style xvector.scp file for training.",
617
+ # )
618
+ parser.add_argument(
619
+ "--train-num-frames",
620
+ default=None,
621
+ type=str,
622
+ help="kaldi-style utt2num_frames file for training.",
623
+ )
624
+ parser.add_argument(
625
+ "--train-utt2spk",
626
+ default=None,
627
+ type=str,
628
+ help="kaldi-style utt2spk file for training.",
629
+ )
630
+ parser.add_argument(
631
+ "--train-spk2gender",
632
+ default=None,
633
+ type=str,
634
+ help="kaldi-style spk2gender file for training.",
635
+ )
636
+ parser.add_argument(
637
+ "--dev-wav-scp",
638
+ default=None,
639
+ type=str,
640
+ help="kaldi-style wav.scp file for validation. "
641
+ )
642
+ parser.add_argument(
643
+ "--dev-idx-scp",
644
+ default=None,
645
+ type=str,
646
+ help="kaldi-style feats.scp file for vaidation. "
647
+ )
648
+ parser.add_argument(
649
+ "--dev-prompt-scp",
650
+ default=None,
651
+ type=str,
652
+ help="kaldi-style feats.scp file for vaidation. "
653
+ )
654
+ parser.add_argument(
655
+ "--dev-target-scp",
656
+ default=None,
657
+ type=str,
658
+ help="kaldi-style feats.scp file for vaidation. "
659
+ )
660
+ parser.add_argument(
661
+ "--dev-aux-scp",
662
+ default=None,
663
+ type=str,
664
+ help="kaldi-style feats.scp file for vaidation. "
665
+ )
666
+ parser.add_argument(
667
+ "--dev-segments",
668
+ default=None,
669
+ type=str,
670
+ help="kaldi-style segments file for validation.",
671
+ )
672
+ # parser.add_argument(
673
+ # "--dev-xvector-scp",
674
+ # default=None,
675
+ # type=str,
676
+ # help="kaldi-style xvector.scp file for validation.",
677
+ # )
678
+ parser.add_argument(
679
+ "--dev-num-frames",
680
+ default=None,
681
+ type=str,
682
+ help="kaldi-style utt2num_frames file for validation.",
683
+ )
684
+ parser.add_argument(
685
+ "--dev-utt2spk",
686
+ default=None,
687
+ type=str,
688
+ help="kaldi-style utt2spk file for training.",
689
+ )
690
+ parser.add_argument(
691
+ "--dev-spk2gender",
692
+ default=None,
693
+ type=str,
694
+ help="kaldi-style spk2gender file for training.",
695
+ )
696
+ parser.add_argument(
697
+ "--outdir",
698
+ type=str,
699
+ required=True,
700
+ help="directory to save checkpoints.",
701
+ )
702
+ parser.add_argument(
703
+ "--config",
704
+ type=str,
705
+ required=True,
706
+ help="yaml format configuration file.",
707
+ )
708
+ parser.add_argument(
709
+ "--pretrain",
710
+ default="",
711
+ type=str,
712
+ nargs="?",
713
+ help='checkpoint file path to load pretrained params. (default="")',
714
+ )
715
+ # parser.add_argument("--pretrain-feature-extractor",
716
+ # default="",
717
+ # type=str,
718
+ # help="checkpoint file path to load pretrained params for CNN+trans feature extractor. Default=''")
719
+ parser.add_argument("--pretrain-vae",
720
+ default="",
721
+ type=str,
722
+ help="pretrained VAE (no VQ)")
723
+ parser.add_argument("--pretrain-codebook",
724
+ default="",
725
+ type=str,
726
+ help="checkpoint file path to load pretrained params for codebook. Default=''")
727
+ # parser.add_argument("--pretrain-ctx-vec2wav",
728
+ # default="",
729
+ # type=str,
730
+ # help="checkpoint file path to load pretrained params for ctx-vec2wav. Default=''")
731
+ parser.add_argument(
732
+ "--resume",
733
+ default="",
734
+ type=str,
735
+ nargs="?",
736
+ help='checkpoint file path to resume training. (default="")',
737
+ )
738
+ parser.add_argument(
739
+ "--verbose",
740
+ type=int,
741
+ default=1,
742
+ help="logging level. higher is more logging. (default=1)",
743
+ )
744
+ # parser.add_argument("--vq-codebook", default=None, type=str)
745
+ parser.add_argument("--sampling-rate", type=int)
746
+ parser.add_argument("--num-mels", type=int)
747
+ parser.add_argument("--hop-size", type=int)
748
+ parser.add_argument("--win-length", type=int)
749
+ args = parser.parse_args()
750
+
751
+ # init distributed training
752
+ device = torch.device("cuda")
753
+ # effective when using fixed size inputs
754
+ # see https://discuss.pytorch.org/t/what-does-torch-backends-cudnn-benchmark-do/5936
755
+ torch.backends.cudnn.benchmark = True
756
+ # setup for distributed training
757
+ # see example: https://github.com/NVIDIA/apex/tree/master/examples/simple/distributed
758
+ if n_gpus == 1:
759
+ assert rank == 0
760
+
761
+ # set logger
762
+ if args.verbose > 1:
763
+ logging.basicConfig(
764
+ level=logging.DEBUG,
765
+ stream=sys.stdout,
766
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
767
+ )
768
+ elif args.verbose > 0:
769
+ logging.basicConfig(
770
+ level=logging.INFO,
771
+ stream=sys.stdout,
772
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
773
+ )
774
+ else:
775
+ logging.basicConfig(
776
+ level=logging.WARN,
777
+ stream=sys.stdout,
778
+ format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
779
+ )
780
+ logging.warning("Skip DEBUG/INFO messages")
781
+
782
+ # check directory existence
783
+ if not os.path.exists(args.outdir):
784
+ os.makedirs(args.outdir)
785
+
786
+ # init process group
787
+ logging.info("Synchronizing between all workers.")
788
+ torch.distributed.init_process_group(backend="nccl", init_method="env://", world_size=n_gpus, rank=rank)
789
+ torch.cuda.set_device(rank)
790
+ logging.info("Finished init process group.")
791
+
792
+ # load and save config
793
+ with open(args.config) as f:
794
+ config = yaml.load(f, Loader=yaml.Loader)
795
+ config.update(vars(args))
796
+ config['rank'] = rank
797
+ config['distributed'] = True
798
+ config['world_size'] = n_gpus
799
+ # config["version"] = lscodec.__version__ # add version info
800
+ if rank == 0:
801
+ with open(os.path.join(args.outdir, "config.yml"), "w") as f:
802
+ yaml.dump(config, f, Dumper=yaml.Dumper)
803
+ for key, value in config.items():
804
+ logging.info(f"{key} = {value}")
805
+
806
+ # get perturber
807
+ perturber = Perturbs(
808
+ formant_f0_perturb_params=config.get("formant_f0_perturb_params", dict()),
809
+ speed_perturb_params=config.get("speed_perturb_params", dict()),
810
+ speed_perturb_prob=config.get("speed_perturb_prob", 1.0)
811
+ )
812
+
813
+ # get dataset
814
+ train_dataset = AudioMelSCPDataset(
815
+ wav_scp=args.train_wav_scp,
816
+ prompt_scp=args.train_prompt_scp,
817
+ target_scp=args.train_target_scp,
818
+ aux_scp=args.train_aux_scp,
819
+ idx_scp=args.train_idx_scp,
820
+ utt2num_frames=args.train_num_frames,
821
+ segments=args.train_segments,
822
+ batch_frames=config.get("batch_frames", None),
823
+ batch_size=config.get("batch_size", None),
824
+ min_num_frames=config.get("min_num_frames", None),
825
+ max_num_frames=config.get("max_num_frames", None),
826
+ allow_cache=config.get("allow_cache", False), # keep compatibility
827
+ length_tolerance=config.get("length_tolerance", 2),
828
+ prompt_min_length=config.get("prompt_min_length", 500),
829
+ extractor_fs=config['feature_extractor_params'].get("fs", 16000),
830
+ # speed_alpha_range=eval(config['speed_alpha_range'])
831
+ utt2spk=args.train_utt2spk,
832
+ spk2gender=args.train_spk2gender,
833
+ perturber=perturber
834
+ )
835
+ if rank == 0:
836
+ logging.info(f"The number of training files = {len(train_dataset)}.")
837
+ dev_dataset = AudioMelSCPDataset(
838
+ wav_scp=args.dev_wav_scp,
839
+ prompt_scp=args.dev_prompt_scp,
840
+ target_scp=args.dev_target_scp,
841
+ aux_scp=args.dev_aux_scp,
842
+ idx_scp=args.dev_idx_scp,
843
+ utt2num_frames=args.dev_num_frames,
844
+ segments=args.dev_segments,
845
+ min_num_frames=config.get("min_num_frames", None),
846
+ max_num_frames=config.get("max_num_frames", None),
847
+ allow_cache=config.get("allow_cache", False), # keep compatibility
848
+ length_tolerance=config.get("length_tolerance", 2),
849
+ prompt_min_length=config.get("prompt_min_length", 500),
850
+ extractor_fs=config['feature_extractor_params'].get("fs", 16000),
851
+ # speed_alpha_range=eval(config['speed_alpha_range'])
852
+ utt2spk=args.dev_utt2spk,
853
+ spk2gender=args.dev_spk2gender,
854
+ perturber=perturber
855
+ )
856
+ if rank == 0:
857
+ logging.info(f"The number of development files = {len(dev_dataset)}.")
858
+ dataset = {
859
+ "train": train_dataset,
860
+ "dev": dev_dataset,
861
+ }
862
+
863
+ # get data loader
864
+ collator = Collator(
865
+ hop_size_for_extractor=config['feature_extractor_params']['hop_size'],
866
+ win_length_for_extractor=config['feature_extractor_params']['win_length'],
867
+ sampling_rate=config["feature_extractor_params"]["fs"],
868
+ n_mel=1024,
869
+ force_from_start=config.get("force_from_start", False)
870
+ )
871
+
872
+ sampler = {
873
+ "train": DistributedSampler(
874
+ dataset=dataset["train"],
875
+ num_replicas=n_gpus,
876
+ rank=rank,
877
+ shuffle=True,
878
+ ),
879
+ "dev": DistributedSampler(
880
+ dataset=dataset["dev"],
881
+ num_replicas=n_gpus,
882
+ rank=rank,
883
+ shuffle=False,
884
+ )}
885
+ data_loader = {
886
+ "train": DataLoader(
887
+ dataset=dataset["train"],
888
+ shuffle=False,
889
+ collate_fn=collator,
890
+ num_workers=config["num_workers"],
891
+ sampler=sampler["train"],
892
+ pin_memory=config["pin_memory"],
893
+ ),
894
+ "dev": DataLoader(
895
+ dataset=dataset["dev"],
896
+ shuffle=False,
897
+ collate_fn=collator,
898
+ num_workers=config["num_workers"],
899
+ sampler=sampler["dev"],
900
+ pin_memory=config["pin_memory"],
901
+ ),
902
+ }
903
+
904
+ from lscodec.utils import load_model
905
+ config['pretrain_codebook'] = args.pretrain_codebook
906
+ generator = load_model(config, None)
907
+
908
+ model = {
909
+ "generator": generator.to(device),
910
+ }
911
+
912
+ # define optimizers and schedulers
913
+ generator_optimizer_class = getattr(
914
+ lscodec.optimizers,
915
+ # keep compatibility
916
+ config.get("generator_optimizer_type", "RAdam"),
917
+ )
918
+ optimizer = {
919
+ "generator": generator_optimizer_class(
920
+ model["generator"].parameters(),
921
+ **config["generator_optimizer_params"],
922
+ ),
923
+ }
924
+ generator_scheduler_class = getattr(
925
+ torch.optim.lr_scheduler,
926
+ # keep compatibility
927
+ config.get("generator_scheduler_type", "StepLR"),
928
+ )
929
+ scheduler = {
930
+ "generator": generator_scheduler_class(
931
+ optimizer=optimizer["generator"],
932
+ **config["generator_scheduler_params"],
933
+ ),
934
+ }
935
+ from torch.nn.parallel import DistributedDataParallel
936
+ model["generator"] = DistributedDataParallel(model["generator"], device_ids=[rank], find_unused_parameters=True)
937
+
938
+ if rank == 0:
939
+ # show settings
940
+ logging.info(model["generator"])
941
+ logging.info(optimizer["generator"])
942
+
943
+ # define trainer
944
+ trainer = Trainer(
945
+ steps=0,
946
+ epochs=0,
947
+ data_loader=data_loader,
948
+ sampler=sampler,
949
+ model=model,
950
+ optimizer=optimizer,
951
+ scheduler=scheduler,
952
+ config=config,
953
+ device=device,
954
+ )
955
+
956
+ if len(args.pretrain_vae) != 0:
957
+ trainer.load_vae_params(args.pretrain_vae)
958
+ if rank == 0:
959
+ logging.info(f"Successfully load parameters from {args.pretrain_vae}.")
960
+
961
+ # load pretrained parameters from checkpoint
962
+ if len(args.pretrain) != 0:
963
+ trainer.load_checkpoint(args.pretrain, load_only_params=True)
964
+ if rank == 0:
965
+ logging.info(f"Successfully load parameters from {args.pretrain}.")
966
+
967
+ if len(args.resume) != 0:
968
+ trainer.load_checkpoint(args.resume)
969
+ if rank == 0:
970
+ logging.info(f"Successfully resumed from {args.resume}.")
971
+
972
+ # run training loop
973
+ try:
974
+ trainer.run()
975
+ finally:
976
+ if rank == 0:
977
+ trainer.save_checkpoint(
978
+ os.path.join(config["outdir"], f"checkpoint-{trainer.steps}steps.pkl")
979
+ )
980
+ logging.info(f"Successfully saved checkpoint @ {trainer.steps}steps.")
981
+
982
+
983
+ if __name__ == "__main__":
984
+ assert torch.cuda.is_available(), "CPU training is not allowed."
985
+ n_gpus = torch.cuda.device_count()
986
+ print(f"============> using {n_gpus} GPUS")
987
+ os.environ["MASTER_ADDR"] = "localhost"
988
+ os.environ["MASTER_PORT"] = "8001"
989
+
990
+ mp.spawn(
991
+ main,
992
+ nprocs=n_gpus,
993
+ args=(n_gpus,)
994
+ )
lscodec/datasets/perturbs.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Portions of this code are based on ContentVec.
4
+ # The original source code can be found at:
5
+ # https://github.com/auspicious3000/contentvec/blob/d746688a32940f4bee410ed7c87ec9cf8ff04f74/contentvec/data/audio/audio_utils_1.py
6
+ #
7
+ # Copyright (c) 2025 Yiwei Guo
8
+ # Licensed under the MIT license.
9
+
10
+ import os
11
+ import numpy as np
12
+ from scipy.signal import sosfilt
13
+ from scipy import signal as sg
14
+ import sox
15
+
16
+
17
+ def make_lowshelf(g, fc, Q, fs=44100):
18
+ """Generate filter coefficients for 2nd order Lowshelf filter.
19
+ This function follows the code from the JUCE DSP library
20
+ which can be found in `juce_IIRFilter.cpp`.
21
+
22
+ The design equations are based upon those found in the Cookbook
23
+ formulae for audio equalizer biquad filter coefficients
24
+ by Robert Bristow-Johnson.
25
+ https://www.w3.org/2011/audio/audio-eq-cookbook.html
26
+ Args:
27
+ g (float): Gain factor in dB.
28
+ fc (float): Cutoff frequency in Hz.
29
+ Q (float): Q factor.
30
+ fs (float): Sampling frequency in Hz.
31
+ Returns:
32
+ tuple: (b, a) filter coefficients
33
+ """
34
+ # convert gain from dB to linear
35
+ g = np.power(10,(g/20))
36
+
37
+ # initial values
38
+ A = np.max([0.0, np.sqrt(g)])
39
+ aminus1 = A - 1
40
+ aplus1 = A + 1
41
+ omega = (2 * np.pi * np.max([fc, 2.0])) / fs
42
+ coso = np.cos(omega)
43
+ beta = np.sin(omega) * np.sqrt(A) / Q
44
+ aminus1TimesCoso = aminus1 * coso
45
+
46
+ # coefs calculation
47
+ b0 = A * (aplus1 - aminus1TimesCoso + beta)
48
+ b1 = A * 2 * (aminus1 - aplus1 * coso)
49
+ b2 = A * (aplus1 - aminus1TimesCoso - beta)
50
+ a0 = aplus1 + aminus1TimesCoso + beta
51
+ a1 = -2 * (aminus1 + aplus1 * coso)
52
+ a2 = aplus1 + aminus1TimesCoso - beta
53
+
54
+ # output coefs
55
+ #b = np.array([b0/a0, b1/a0, b2/a0])
56
+ #a = np.array([a0/a0, a1/a0, a2/a0])
57
+
58
+ return np.array([[b0/a0, b1/a0, b2/a0, 1.0, a1/a0, a2/a0]])
59
+
60
+
61
+
62
+ def make_highself(g, fc, Q, fs=44100):
63
+ """Generate filter coefficients for 2nd order Highshelf filter.
64
+ This function follows the code from the JUCE DSP library
65
+ which can be found in `juce_IIRFilter.cpp`.
66
+
67
+ The design equations are based upon those found in the Cookbook
68
+ formulae for audio equalizer biquad filter coefficients
69
+ by Robert Bristow-Johnson.
70
+ https://www.w3.org/2011/audio/audio-eq-cookbook.html
71
+ Args:
72
+ g (float): Gain factor in dB.
73
+ fc (float): Cutoff frequency in Hz.
74
+ Q (float): Q factor.
75
+ fs (float): Sampling frequency in Hz.
76
+ Returns:
77
+ tuple: (b, a) filter coefficients
78
+ """
79
+ # convert gain from dB to linear
80
+ g = np.power(10,(g/20))
81
+
82
+ # initial values
83
+ A = np.max([0.0, np.sqrt(g)])
84
+ aminus1 = A - 1
85
+ aplus1 = A + 1
86
+ omega = (2 * np.pi * np.max([fc, 2.0])) / fs
87
+ coso = np.cos(omega)
88
+ beta = np.sin(omega) * np.sqrt(A) / Q
89
+ aminus1TimesCoso = aminus1 * coso
90
+
91
+ # coefs calculation
92
+ b0 = A * (aplus1 + aminus1TimesCoso + beta)
93
+ b1 = A * -2 * (aminus1 + aplus1 * coso)
94
+ b2 = A * (aplus1 + aminus1TimesCoso - beta)
95
+ a0 = aplus1 - aminus1TimesCoso + beta
96
+ a1 = 2 * (aminus1 - aplus1 * coso)
97
+ a2 = aplus1 - aminus1TimesCoso - beta
98
+
99
+ # output coefs
100
+ #b = np.array([b0/a0, b1/a0, b2/a0])
101
+ #a = np.array([a0/a0, a1/a0, a2/a0])
102
+
103
+ return np.array([[b0/a0, b1/a0, b2/a0, 1.0, a1/a0, a2/a0]])
104
+
105
+
106
+
107
+ def make_peaking(g, fc, Q, fs=44100):
108
+ """Generate filter coefficients for 2nd order Peaking EQ.
109
+ This function follows the code from the JUCE DSP library
110
+ which can be found in `juce_IIRFilter.cpp`.
111
+
112
+ The design equations are based upon those found in the Cookbook
113
+ formulae for audio equalizer biquad filter coefficients
114
+ by Robert Bristow-Johnson.
115
+ https://www.w3.org/2011/audio/audio-eq-cookbook.html
116
+ Args:
117
+ g (float): Gain factor in dB.
118
+ fc (float): Cutoff frequency in Hz.
119
+ Q (float): Q factor.
120
+ fs (float): Sampling frequency in Hz.
121
+ Returns:
122
+ tuple: (b, a) filter coefficients
123
+ """
124
+ # convert gain from dB to linear
125
+ g = np.power(10,(g/20))
126
+
127
+ # initial values
128
+ A = np.max([0.0, np.sqrt(g)])
129
+ omega = (2 * np.pi * np.max([fc, 2.0])) / fs
130
+ alpha = np.sin(omega) / (Q * 2)
131
+ c2 = -2 * np.cos(omega)
132
+ alphaTimesA = alpha * A
133
+ alphaOverA = alpha / A
134
+
135
+ # coefs calculation
136
+ b0 = 1 + alphaTimesA
137
+ b1 = c2
138
+ b2 = 1 - alphaTimesA
139
+ a0 = 1 + alphaOverA
140
+ a1 = c2
141
+ a2 = 1 - alphaOverA
142
+
143
+ # output coefs
144
+ #b = np.array([b0/a0, b1/a0, b2/a0])
145
+ #a = np.array([a0/a0, a1/a0, a2/a0])
146
+
147
+ return np.array([[b0/a0, b1/a0, b2/a0, 1.0, a1/a0, a2/a0]])
148
+
149
+
150
+
151
+ def params2sos(G, Fc, Q, fs):
152
+ """Convert 5 band EQ paramaters to 2nd order sections.
153
+ Takes a vector with shape (13,) of denormalized EQ parameters
154
+ and calculates filter coefficients for each of the 5 filters.
155
+ These coefficients (2nd order sections) are then stored into a
156
+ single (5,6) matrix. This matrix can be fed to `scipy.signal.sosfreqz()`
157
+ in order to determine the frequency response of the cascasd of
158
+ all five biquad filters.
159
+ Args:
160
+ x (float): Gain factor in dB.
161
+ fs (float): Sampling frequency in Hz.
162
+ Returns:
163
+ ndarray: filter coefficients for 5 band EQ stored in (5,6) matrix.
164
+ [[b1_0, b1_1, b1_2, a1_0, a1_1, a1_2], # lowshelf coefficients
165
+ [b2_0, b2_1, b2_2, a2_0, a2_1, a2_2], # first band coefficients
166
+ [b3_0, b3_1, b3_2, a3_0, a3_1, a3_2], # second band coefficients
167
+ [b4_0, b4_1, b4_2, a4_0, a4_1, a4_2], # third band coefficients
168
+ [b5_0, b5_1, b5_2, a5_0, a5_1, a5_2]] # highshelf coefficients
169
+ """
170
+ # generate filter coefficients from eq params
171
+ c0 = make_lowshelf(G[0], Fc[0], Q[0], fs=fs)
172
+ c1 = make_peaking (G[1], Fc[1], Q[1], fs=fs)
173
+ c2 = make_peaking (G[2], Fc[2], Q[2], fs=fs)
174
+ c3 = make_peaking (G[3], Fc[3], Q[3], fs=fs)
175
+ c4 = make_peaking (G[4], Fc[4], Q[4], fs=fs)
176
+ c5 = make_peaking (G[5], Fc[5], Q[5], fs=fs)
177
+ c6 = make_peaking (G[6], Fc[6], Q[6], fs=fs)
178
+ c7 = make_peaking (G[7], Fc[7], Q[7], fs=fs)
179
+ c8 = make_peaking (G[8], Fc[8], Q[8], fs=fs)
180
+ c9 = make_highself(G[9], Fc[9], Q[9], fs=fs)
181
+
182
+ # stuff coefficients into second order sections structure
183
+ sos = np.concatenate([c0,c1,c2,c3,c4,c5,c6,c7,c8,c9], axis=0)
184
+
185
+ return sos
186
+
187
+
188
+ import parselmouth
189
+ def change_gender(x, fs, lo, hi, ratio_fs, ratio_ps, ratio_pr):
190
+ s = parselmouth.Sound(x, sampling_frequency=fs)
191
+ f0 = s.to_pitch_ac(pitch_floor=lo, pitch_ceiling=hi, time_step=0.8/lo)
192
+ f0_np = f0.selected_array['frequency']
193
+ f0_med = np.median(f0_np[f0_np!=0]).item()
194
+ ss = parselmouth.praat.call([s, f0], "Change gender", ratio_fs, f0_med*ratio_ps, ratio_pr, 1.0)
195
+ return ss.values.squeeze(0)
196
+
197
+ def change_gender_f0(x, fs, lo, hi, ratio_fs, new_f0_med, ratio_pr):
198
+ s = parselmouth.Sound(x, sampling_frequency=fs)
199
+ ss = parselmouth.praat.call(s, "Change gender", lo, hi, ratio_fs, new_f0_med, ratio_pr, 1.0)
200
+ return ss.values.squeeze(0)
201
+
202
+ def parse_range(range_str):
203
+ # (a,b)|(c,d)|p
204
+
205
+ terms = range_str.split("|")
206
+ assert len(terms)==3, range_str
207
+ prob = float(terms[-1])
208
+ ranges = [eval(terms[0]), eval(terms[1])]
209
+ return ranges, prob
210
+
211
+ class FormantF0Perturb:
212
+ def __init__(self, male_formant_range, female_formant_range, female_f0_range):
213
+ # male_formant_range: (0.9,0.95)|(1.05,1.3)|0.25
214
+ # first range, second range, prob of first range
215
+
216
+ self.Qmin = 2
217
+ self.Qmax = 5
218
+ self.male_formant_range, self.male_formant_prob = parse_range(male_formant_range)
219
+ self.female_formant_range, self.female_formant_prob = parse_range(female_formant_range)
220
+ self.female_f0_range, self.female_f0_prob = parse_range(female_f0_range)
221
+
222
+ def get_perturb_param(self, gender):
223
+ # formant shift and pitch shift.
224
+ if gender == "M":
225
+ if np.random.random()<=self.male_formant_prob:
226
+ ratio_fs = np.random.uniform(self.male_formant_range[0][0], self.male_formant_range[0][1])
227
+ else:
228
+ ratio_fs = np.random.uniform(self.male_formant_range[1][0], self.male_formant_range[1][1])
229
+ return 75, 250, ratio_fs, 1.0
230
+ else:
231
+ if np.random.random()<=self.female_formant_prob:
232
+ ratio_fs = np.random.uniform(self.female_formant_range[0][0], self.female_formant_range[0][1])
233
+ else:
234
+ ratio_fs = np.random.uniform(self.female_formant_range[1][0], self.female_formant_range[1][1])
235
+ if np.random.random()<=self.female_f0_prob:
236
+ ratio_ps = np.random.uniform(self.female_f0_range[0][0], self.female_f0_range[0][1])
237
+ else:
238
+ ratio_ps = np.random.uniform(self.female_f0_range[1][0], self.female_f0_range[1][1])
239
+ return 100, 400, ratio_fs, ratio_ps
240
+
241
+ def random_eq(self, wav, sr):
242
+ Fc = np.exp(np.linspace(np.log(60), np.log(7600), 10))
243
+ # rng = np.random.default_rng()
244
+ z = np.random.uniform(0, 1, size=(10,))
245
+ Q = self.Qmin * (self.Qmax / self.Qmin)**z
246
+ G = np.random.uniform(-12, 12, size=(10,))
247
+ sos = params2sos(G, Fc, Q, sr)
248
+ wav = sosfilt(sos, wav)
249
+ return wav
250
+
251
+ def __call__(self, wav, sr, gender):
252
+ # gender: "F" or "M"
253
+
254
+ lo, hi, ratio_fs, ratio_ps = self.get_perturb_param(gender)
255
+ # fs: formant shift. This controls timbre (higher -> more female). ps: pitch shift. This controls absolute pitch level
256
+ # We fix the pitch range parameter to 1. So that we don't change the pitch variations
257
+ ratio_pr = 1
258
+
259
+ ss = change_gender(wav, sr, lo, hi, ratio_fs, ratio_ps, ratio_pr)
260
+
261
+ return ss
262
+
263
+ class SpeedPerturb:
264
+ def __init__(self, male_range, female_range):
265
+ self.male_range, self.male_prob = parse_range(male_range)
266
+ self.female_range, self.female_prob = parse_range(female_range)
267
+
268
+ def __call__(self, wav, sr, gender):
269
+
270
+ if gender == "F":
271
+ ranges, prob = self.male_range, self.male_prob
272
+ else:
273
+ ranges, prob = self.female_range, self.female_prob
274
+
275
+ if np.random.random()<=prob:
276
+ speed_alpha = np.random.uniform(ranges[0][0], ranges[0][1])
277
+ else:
278
+ speed_alpha = np.random.uniform(ranges[1][0], ranges[1][1])
279
+
280
+ self.transformer = sox.Transformer()
281
+ self.transformer.set_globals(verbosity=1) # disable warnings.
282
+ self.transformer.speed(factor=speed_alpha)
283
+ self.transformer.tempo(factor=1/speed_alpha) # recover the original length. Only affects timbre and pitch.
284
+ wav_perturb = self.transformer.build_array(input_array=wav, sample_rate_in=sr)
285
+ return wav_perturb
286
+
287
+ class IdentityPerturb:
288
+ def __init__(self, ):
289
+ pass
290
+ def __call__(self,wav, sr, gender):
291
+ return wav
292
+
293
+ class Perturbs:
294
+ def __init__(self, formant_f0_perturb_params, speed_perturb_params, speed_perturb_prob):
295
+ self.speed_perturb_prob = speed_perturb_prob
296
+ if speed_perturb_prob!=0.0:
297
+ self.speed_perturber = SpeedPerturb(**speed_perturb_params)
298
+ else:
299
+ self.speed_perturber = IdentityPerturb()
300
+ if speed_perturb_prob!=1.0:
301
+ self.formant_perturber = FormantF0Perturb(**formant_f0_perturb_params)
302
+ else:
303
+ self.formant_perturber = IdentityPerturb()
304
+ def __call__(self, wav, sr, gender):
305
+ if np.random.random()<=self.speed_perturb_prob:
306
+ return self.speed_perturber(wav,sr,gender)
307
+ else:
308
+ return self.formant_perturber(wav,sr,gender)
lscodec/datasets/scp_dataset.py ADDED
@@ -0,0 +1,381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Copyright 2019 Tomoki Hayashi
4
+ # MIT License (https://opensource.org/licenses/MIT)
5
+
6
+ # Portions of this code are a modification of the ParallelWaveGAN project.
7
+ # The original source code can be found at:
8
+ # https://github.com/kan-bayashi/ParallelWaveGAN/blob/master/parallel_wavegan/datasets/scp_dataset.py
9
+ #
10
+ # Copyright (c) 2025 Yiwei Guo
11
+ # Licensed under the MIT license.
12
+
13
+
14
+ """Dataset modules based on kaldi-style scp files."""
15
+
16
+ import torch
17
+ import logging
18
+ import random
19
+ import copy
20
+ import sox
21
+ from multiprocessing import Manager
22
+
23
+ import kaldiio
24
+ import librosa
25
+ import numpy as np
26
+
27
+ from torch.utils.data import Dataset
28
+ from tqdm import tqdm
29
+ from lscodec.utils import HDF5ScpLoader
30
+ from lscodec.utils import NpyScpLoader
31
+ from lscodec.datasets.perturbs import Perturbs
32
+
33
+ logging.getLogger("sox").setLevel(logging.ERROR)
34
+
35
+
36
+ def _get_feats_scp_loader(feats_scp):
37
+ # read the first line of feats.scp file
38
+ with open(feats_scp) as f:
39
+ key, value = f.readlines()[0].replace("\n", "").split()
40
+
41
+ # check scp type
42
+ if ":" in value:
43
+ value_1, value_2 = value.split(":")
44
+ if value_1.endswith(".ark"):
45
+ # kaldi-ark case: utt_id_1 /path/to/utt_id_1.ark:index
46
+ return kaldiio.load_scp(feats_scp)
47
+ elif value_1.endswith(".h5"):
48
+ # hdf5 case with path in hdf5: utt_id_1 /path/to/utt_id_1.h5:feats
49
+ return HDF5ScpLoader(feats_scp)
50
+ else:
51
+ raise ValueError("Not supported feats.scp type.")
52
+ else:
53
+ if value.endswith(".h5"):
54
+ # hdf5 case without path in hdf5: utt_id_1 /path/to/utt_id_1.h5
55
+ return HDF5ScpLoader(feats_scp)
56
+ elif value.endswith(".npy"):
57
+ # npy case: utt_id_1 /path/to/utt_id_1.npy
58
+ return NpyScpLoader(feats_scp)
59
+ else:
60
+ raise ValueError("Not supported feats.scp type.")
61
+
62
+
63
+ class AudioMelSCPDataset(Dataset):
64
+ """PyTorch compatible audio and feat dataset based on kaldi-stype scp files."""
65
+
66
+ def __init__(
67
+ self,
68
+ wav_scp,
69
+ prompt_scp,
70
+ target_scp,
71
+ aux_scp,
72
+ idx_scp,
73
+ utt2num_frames=None,
74
+ segments=None,
75
+ batch_frames=None,
76
+ batch_size=None,
77
+ min_num_frames=None,
78
+ max_num_frames=None,
79
+ return_utt_id=False,
80
+ return_sampling_rate=False,
81
+ allow_cache=False,
82
+ length_tolerance=2,
83
+ prompt_min_length=500, # in terms of frames
84
+ extractor_fs=16000,
85
+ # speed_alpha_range=(0.8, 1.2),
86
+ perturber=None,
87
+ utt2spk=None,
88
+ spk2gender=None
89
+ ):
90
+ """Initialize dataset.
91
+
92
+ Args:
93
+ wav_scp (str): Kaldi-style wav.scp file.
94
+ prompt_scp (str): Kaldi-style fests.scp file.
95
+ aux_scp (str): Kaldi-style fests.scp file.
96
+ segments (str): Kaldi-style segments file.
97
+ min_num_frames (int): Threshold to remove short feature files.
98
+ max_num_frames (int): Threshold to remove long feature files.
99
+ return_utt_id (bool): Whether to return utterance id.
100
+ return_sampling_rate (bool): Whether to return sampling rate.
101
+ allow_cache (bool): Whether to allow cache of the loaded files.
102
+
103
+ """
104
+ # load scp as lazy dict
105
+ self.audio_loader = kaldiio.load_scp(wav_scp, segments=segments)
106
+ self.prompt_loader = _get_feats_scp_loader(prompt_scp)
107
+ # with open(prompt_scp, 'r') as fr:
108
+ # self.prompt_loader = dict()
109
+ # for line in fr.readlines():
110
+ # terms = line.strip().split()
111
+ # utt, path = terms
112
+ # self.prompt_loader[utt] = path
113
+
114
+ self.target_loader = _get_feats_scp_loader(target_scp)
115
+ self.aux_loader = _get_feats_scp_loader(aux_scp)
116
+ self.idx_loader = _get_feats_scp_loader(idx_scp)
117
+
118
+ self.utt_ids = list(self.prompt_loader.keys())
119
+ self.return_utt_id = return_utt_id
120
+ self.return_sampling_rate = return_sampling_rate
121
+ self.allow_cache = allow_cache
122
+ self.extractor_fs = extractor_fs
123
+
124
+ utt2num_frames_loader = None
125
+ if utt2num_frames is not None:
126
+ with open(utt2num_frames, 'r') as f:
127
+ utt2num_frames_loader = dict([(x.split()[0], int(x.split()[1])) for x in f.readlines()])
128
+ else:
129
+ utt2num_frames_loader = dict([(k, mel.shape[0]) for k, mel in self.prompt_loader.items()])
130
+ self.utt2num_frames_loader = utt2num_frames_loader
131
+ # filter by threshold
132
+ if (min_num_frames or max_num_frames) is not None:
133
+ mel_lengths = [utt2num_frames_loader[key] for key in self.utt_ids]
134
+ idxs = [
135
+ idx
136
+ for idx in range(len(self.utt_ids))
137
+ if (min_num_frames and mel_lengths[idx] >= min_num_frames) and (max_num_frames and mel_lengths[idx] <= max_num_frames)
138
+ ]
139
+ if len(self.utt_ids) != len(idxs):
140
+ logging.warning(
141
+ f"Some files are filtered by mel length threshold "
142
+ f"({len(self.utt_ids)} -> {len(idxs)})."
143
+ )
144
+ self.utt_ids = [self.utt_ids[idx] for idx in idxs]
145
+
146
+ # batchify
147
+ if batch_frames is not None:
148
+ self.batches = self.batchify(utt2num_frames_loader, batch_frames=batch_frames)
149
+ elif batch_size is not None:
150
+ self.batches = self.batchify(utt2num_frames_loader, batch_size=batch_size)
151
+ else:
152
+ self.batches = [[utt_id] for utt_id in self.utt_ids]
153
+
154
+ if allow_cache:
155
+ # NOTE(kan-bayashi): Manager is need to share memory in dataloader with num_workers > 0
156
+ self.manager = Manager()
157
+ self.caches = self.manager.dict()
158
+ self.length_tolerance = length_tolerance
159
+ self.prompt_min_length = prompt_min_length
160
+
161
+ # assert len(speed_alpha_range)==2, speed_alpha_range
162
+ # self.alpha_range = speed_alpha_range
163
+ self.perturber = perturber
164
+
165
+ if utt2spk is not None:
166
+ self.utt2spk = dict()
167
+ with open(utt2spk, 'r') as f:
168
+ for line in f.readlines():
169
+ terms = line.strip().split()
170
+ self.utt2spk[terms[0]] = terms[1]
171
+ assert spk2gender is not None
172
+ self.spk2gender = dict()
173
+ with open(spk2gender, 'r') as f:
174
+ for line in f.readlines():
175
+ terms = line.strip().split()
176
+ assert terms[1] in {"F", "M"}
177
+ self.spk2gender[terms[0]] = terms[1]
178
+ else:
179
+ self.utt2spk = None
180
+
181
+ def batchify(self, utt2num_frames_loader, batch_frames=None, batch_size=None, min_batch_size=1, drop_last=True):
182
+
183
+ assert batch_size is None or batch_size > min_batch_size
184
+
185
+ batches = []
186
+ batch = []
187
+ accum_num_frames = 0
188
+ utt_ids_set = set(self.utt_ids)
189
+ for utt_id, mel_length in tqdm(sorted(list(utt2num_frames_loader.items()), key=lambda x: x[1], reverse=True)):
190
+ if utt_id not in utt_ids_set:
191
+ continue
192
+ if (batch_frames is not None and accum_num_frames + mel_length > batch_frames and len(batch) > min_batch_size) or (batch_size is not None and len(batch) == batch_size):
193
+ batches.append(batch)
194
+ batch = []
195
+ accum_num_frames = 0
196
+ batch.append(utt_id)
197
+ accum_num_frames += mel_length
198
+ if len(batch) > min_batch_size and not drop_last:
199
+ batches.append(batch)
200
+ return batches
201
+
202
+ def __getitem__(self, idx):
203
+ """Get specified idx items.
204
+
205
+ Args:
206
+ idx (int): Index of the item.
207
+
208
+ Returns:
209
+ str: Utterance id (only in return_utt_id = True).
210
+ ndarray or tuple: Audio signal (T,) or (w/ sampling rate if return_sampling_rate = True).
211
+ ndarray: Feature (T', C).
212
+
213
+ """
214
+ batch = self.batches[idx]
215
+ batch_items = []
216
+
217
+ for utt_id in batch:
218
+ if self.allow_cache and self.caches.get(utt_id) is not None:
219
+ items = self.caches[utt_id]
220
+ else:
221
+ fs, audio = self.audio_loader[utt_id]
222
+ prompt = self.prompt_loader[utt_id]
223
+ # prompt = torch.load(prompt_path).numpy()
224
+ target = self.target_loader[utt_id]
225
+ ssl_idx = self.idx_loader[utt_id]
226
+ aux = self.aux_loader[utt_id]
227
+
228
+ min_len = min(len(prompt), len(aux), len(target), len(ssl_idx))
229
+ assert ((abs(len(prompt) - min_len) <= self.length_tolerance) and
230
+ (abs(len(target) - min_len) <= self.length_tolerance) and
231
+ (abs(len(ssl_idx) - min_len) <= self.length_tolerance) and
232
+ (abs(len(aux) - min_len) <= self.length_tolerance)), \
233
+ f"Audio feature lengths difference exceeds length tolerance for {utt_id}"
234
+ prompt, aux, target, ssl_idx = prompt[:min_len], aux[:min_len], target[:min_len], ssl_idx[:min_len]
235
+
236
+ if self.utt2spk is not None:
237
+ spk=self.utt2spk[utt_id]
238
+ gender=self.spk2gender[spk]
239
+ else:
240
+ gender = "M" # Default to male, so that not use pitch shift
241
+
242
+ # normalize audio signal to be [-1, 1]
243
+ audio = audio.astype(np.float32)
244
+ audio /= 1 << (16 - 1) # assume that wav is PCM 16 bit
245
+
246
+ if fs != self.extractor_fs:
247
+ audio_for_extractor = librosa.resample(audio, orig_sr=fs, target_sr=self.extractor_fs)
248
+ else:
249
+ audio_for_extractor = audio
250
+
251
+ # # perturb
252
+ # if self.alpha_range[0] == self.alpha_range[1] == 1.0:
253
+ # pass
254
+ # else:
255
+ # speed_alpha = random.random() * (self.alpha_range[1] - self.alpha_range[0]) + self.alpha_range[0]
256
+ # self.transformer = sox.Transformer()
257
+ # self.transformer.set_globals(verbosity=1) # disable warnings.
258
+ # self.transformer.speed(factor=speed_alpha)
259
+ # self.transformer.tempo(factor=1/speed_alpha) # recover the original length. Only affects timbre and pitch.
260
+ # audio_for_extractor = self.transformer.build_array(input_array=audio_for_extractor, sample_rate_in=self.extractor_fs)
261
+ audio_for_extractor = self.perturber(audio_for_extractor, self.extractor_fs, gender)
262
+
263
+ if self.return_utt_id:
264
+ items = utt_id, audio_for_extractor, prompt, aux, target, ssl_idx
265
+ else:
266
+ items = audio_for_extractor, prompt, aux, target, ssl_idx
267
+
268
+ if self.allow_cache:
269
+ self.caches[utt_id] = items
270
+
271
+ batch_items.append(items)
272
+
273
+ return batch_items
274
+
275
+ def __len__(self):
276
+ """Return dataset length.
277
+ Returns:
278
+ int: The length of dataset.
279
+ """
280
+ return len(self.batches)
281
+
282
+
283
+ class MelSCPDataset(Dataset):
284
+ """PyTorch compatible feat dataset based on kaldi-stype scp files."""
285
+
286
+ def __init__(
287
+ self,
288
+ vqidx_scp,
289
+ prompt_scp,
290
+ utt2num_frames=None,
291
+ min_num_frames=None,
292
+ max_num_frames=None,
293
+ return_utt_id=False,
294
+ allow_cache=False,
295
+ ):
296
+ """Initialize dataset.
297
+
298
+ Args:
299
+ vqidx_scp (str): Kaldi-style fests.scp file.
300
+ prompt_scp (str): Kaldi-style scp file. In this file, every utt is associated with its prompt's mel-spectrogram.
301
+ min_num_frames (int): Threshold to remove short feature files.
302
+ max_num_frames (int): Threshold to remove long feature files.
303
+ return_utt_id (bool): Whether to return utterance id.
304
+ allow_cache (bool): Whether to allow cache of the loaded files.
305
+ """
306
+ # load scp as lazy dict
307
+ vqidx_loader = _get_feats_scp_loader(vqidx_scp)
308
+ prompt_loader = _get_feats_scp_loader(prompt_scp)
309
+ vqidx_keys = list(set(prompt_loader.keys()) & set(vqidx_loader.keys()))
310
+
311
+ utt2num_frames_loader = None
312
+ if utt2num_frames is not None:
313
+ with open(utt2num_frames, 'r') as f:
314
+ utt2num_frames_loader = dict([(x.split()[0], int(x.split()[1])) for x in f.readlines()])
315
+ else:
316
+ utt2num_frames_loader = dict([(k, mel.shape[0]) for k, mel in vqidx_loader.items()])
317
+
318
+ # filter by threshold
319
+ if (min_num_frames or max_num_frames) is not None:
320
+ mel_lengths = [utt2num_frames_loader[key] for key in vqidx_keys]
321
+ idxs = [
322
+ idx
323
+ for idx in range(len(vqidx_keys))
324
+ if (min_num_frames and mel_lengths[idx] > min_num_frames) and (max_num_frames and mel_lengths[idx] < max_num_frames)
325
+ ]
326
+ if len(vqidx_keys) != len(idxs):
327
+ logging.warning(
328
+ f"Some files are filtered by mel length threshold "
329
+ f"({len(vqidx_keys)} -> {len(idxs)})."
330
+ )
331
+ vqidx_keys = [vqidx_keys[idx] for idx in idxs]
332
+
333
+ self.vqidx_loader = vqidx_loader
334
+ self.prompt_loader = prompt_loader
335
+ self.utt_ids = vqidx_keys
336
+ self.return_utt_id = return_utt_id
337
+ self.allow_cache = allow_cache
338
+
339
+ if allow_cache:
340
+ # NOTE(kan-bayashi): Manager is need to share memory in dataloader with num_workers > 0
341
+ self.manager = Manager()
342
+ self.caches = self.manager.list()
343
+ self.caches += [() for _ in range(len(self.utt_ids))]
344
+
345
+ def __getitem__(self, idx):
346
+ """Get specified idx items.
347
+
348
+ Args:
349
+ idx (int): Index of the item.
350
+
351
+ Returns:
352
+ str: Utterance id (only in return_utt_id = True).
353
+ ndarray: Feature (T', C).
354
+
355
+ """
356
+ if self.allow_cache and len(self.caches[idx]) != 0:
357
+ return self.caches[idx]
358
+
359
+ utt_id = self.utt_ids[idx]
360
+ vqidx = self.vqidx_loader[utt_id].astype(int)
361
+
362
+ prompt = self.prompt_loader[utt_id].copy()
363
+
364
+ if self.return_utt_id:
365
+ items = utt_id, vqidx, prompt
366
+ else:
367
+ items = vqidx, prompt
368
+
369
+ if self.allow_cache:
370
+ self.caches[idx] = items
371
+
372
+ return items
373
+
374
+ def __len__(self):
375
+ """Return dataset length.
376
+
377
+ Returns:
378
+ int: The length of dataset.
379
+
380
+ """
381
+ return len(self.utt_ids)
lscodec/distributed/__init__.py ADDED
File without changes
lscodec/distributed/distrib.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Torch distributed utilities."""
8
+
9
+ import typing as tp
10
+
11
+ import torch
12
+
13
+
14
+ def rank():
15
+ if torch.distributed.is_initialized():
16
+ return torch.distributed.get_rank()
17
+ else:
18
+ return 0
19
+
20
+
21
+ def world_size():
22
+ if torch.distributed.is_initialized():
23
+ return torch.distributed.get_world_size()
24
+ else:
25
+ return 1
26
+
27
+
28
+ def is_distributed():
29
+ return world_size() > 1
30
+
31
+
32
+ def all_reduce(tensor: torch.Tensor, op=torch.distributed.ReduceOp.SUM):
33
+ if is_distributed():
34
+ return torch.distributed.all_reduce(tensor, op)
35
+
36
+
37
+ def _is_complex_or_float(tensor):
38
+ return torch.is_floating_point(tensor) or torch.is_complex(tensor)
39
+
40
+
41
+ def _check_number_of_params(params: tp.List[torch.Tensor]):
42
+ # utility function to check that the number of params in all workers is the same,
43
+ # and thus avoid a deadlock with distributed all reduce.
44
+ if not is_distributed() or not params:
45
+ return
46
+ tensor = torch.tensor([len(params)], device=params[0].device, dtype=torch.long)
47
+ all_reduce(tensor)
48
+ if tensor.item() != len(params) * world_size():
49
+ # If not all the workers have the same number, for at least one of them,
50
+ # this inequality will be verified.
51
+ raise RuntimeError(f"Mismatch in number of params: ours is {len(params)}, "
52
+ "at least one worker has a different one.")
53
+
54
+
55
+ def broadcast_tensors(tensors: tp.Iterable[torch.Tensor], src: int = 0):
56
+ """Broadcast the tensors from the given parameters to all workers.
57
+ This can be used to ensure that all workers have the same model to start with.
58
+ """
59
+ if not is_distributed():
60
+ return
61
+ tensors = [tensor for tensor in tensors if _is_complex_or_float(tensor)]
62
+ _check_number_of_params(tensors)
63
+ handles = []
64
+ for tensor in tensors:
65
+ handle = torch.distributed.broadcast(tensor.data, src=src, async_op=True)
66
+ handles.append(handle)
67
+ for handle in handles:
68
+ handle.wait()
69
+
70
+
71
+ def sync_buffer(buffers, average=True):
72
+ """
73
+ Sync grad for buffers. If average is False, broadcast instead of averaging.
74
+ """
75
+ if not is_distributed():
76
+ return
77
+ handles = []
78
+ for buffer in buffers:
79
+ if torch.is_floating_point(buffer.data):
80
+ if average:
81
+ handle = torch.distributed.all_reduce(
82
+ buffer.data, op=torch.distributed.ReduceOp.SUM, async_op=True)
83
+ else:
84
+ handle = torch.distributed.broadcast(
85
+ buffer.data, src=0, async_op=True)
86
+ handles.append((buffer, handle))
87
+ for buffer, handle in handles:
88
+ handle.wait()
89
+ if average:
90
+ buffer.data /= world_size
91
+
92
+
93
+ def sync_grad(params):
94
+ """
95
+ Simpler alternative to DistributedDataParallel, that doesn't rely
96
+ on any black magic. For simple models it can also be as fast.
97
+ Just call this on your model parameters after the call to backward!
98
+ """
99
+ if not is_distributed():
100
+ return
101
+ handles = []
102
+ for p in params:
103
+ if p.grad is not None:
104
+ handle = torch.distributed.all_reduce(
105
+ p.grad.data, op=torch.distributed.ReduceOp.SUM, async_op=True)
106
+ handles.append((p, handle))
107
+ for p, handle in handles:
108
+ handle.wait()
109
+ p.grad.data /= world_size()
110
+
111
+
112
+ def average_metrics(metrics: tp.Dict[str, float], count=1.):
113
+ """Average a dictionary of metrics across all workers, using the optional
114
+ `count` as unormalized weight.
115
+ """
116
+ if not is_distributed():
117
+ return metrics
118
+ keys, values = zip(*metrics.items())
119
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
120
+ tensor = torch.tensor(list(values) + [1], device=device, dtype=torch.float32)
121
+ tensor *= count
122
+ all_reduce(tensor)
123
+ averaged = (tensor[:-1] / tensor[-1]).cpu().tolist()
124
+ return dict(zip(keys, averaged))
lscodec/distributed/launch.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """Distributed process launcher.
5
+
6
+ This code is modified from https://github.com/pytorch/pytorch/blob/v1.3.0/torch/distributed/launch.py.
7
+
8
+ """
9
+ import os
10
+ import subprocess
11
+ import sys
12
+
13
+ from argparse import ArgumentParser
14
+ from argparse import REMAINDER
15
+
16
+
17
+ def parse_args():
18
+ """Parse arguments."""
19
+ parser = ArgumentParser(
20
+ description="PyTorch distributed training launch "
21
+ "helper utilty that will spawn up "
22
+ "multiple distributed processes"
23
+ )
24
+
25
+ # Optional arguments for the launch helper
26
+ parser.add_argument(
27
+ "--nnodes",
28
+ type=int,
29
+ default=1,
30
+ help="The number of nodes to use for distributed " "training",
31
+ )
32
+ parser.add_argument(
33
+ "--node_rank",
34
+ type=int,
35
+ default=0,
36
+ help="The rank of the node for multi-node distributed " "training",
37
+ )
38
+ parser.add_argument(
39
+ "--nproc_per_node",
40
+ type=int,
41
+ default=1,
42
+ help="The number of processes to launch on each node, "
43
+ "for GPU training, this is recommended to be set "
44
+ "to the number of GPUs in your system so that "
45
+ "each process can be bound to a single GPU.",
46
+ )
47
+ parser.add_argument(
48
+ "--master_addr",
49
+ default="127.0.0.1",
50
+ type=str,
51
+ help="Master node (rank 0)'s address, should be either "
52
+ "the IP address or the hostname of node 0, for "
53
+ "single node multi-proc training, the "
54
+ "--master_addr can simply be 127.0.0.1",
55
+ )
56
+ parser.add_argument(
57
+ "--master_port",
58
+ default=29500,
59
+ type=int,
60
+ help="Master node (rank 0)'s free port that needs to "
61
+ "be used for communciation during distributed "
62
+ "training",
63
+ )
64
+ parser.add_argument(
65
+ "--use_env",
66
+ default=False,
67
+ action="store_true",
68
+ help="Use environment variable to pass "
69
+ "'local rank'. For legacy reasons, the default value is False. "
70
+ "If set to True, the script will not pass "
71
+ "--local_rank as argument, and will instead set LOCAL_RANK.",
72
+ )
73
+ parser.add_argument(
74
+ "-m",
75
+ "--module",
76
+ default=False,
77
+ action="store_true",
78
+ help="Changes each process to interpret the launch script "
79
+ "as a python module, executing with the same behavior as"
80
+ "'python -m'.",
81
+ )
82
+ parser.add_argument(
83
+ "-c",
84
+ "--command",
85
+ default=False,
86
+ action="store_true",
87
+ help="Changes each process to interpret the launch script " "as a command.",
88
+ )
89
+
90
+ # positional
91
+ parser.add_argument(
92
+ "training_script",
93
+ type=str,
94
+ help="The full path to the single GPU training "
95
+ "program/script/command to be launched in parallel, "
96
+ "followed by all the arguments for the "
97
+ "training script",
98
+ )
99
+
100
+ # rest from the training program
101
+ parser.add_argument("training_script_args", nargs=REMAINDER)
102
+ return parser.parse_args()
103
+
104
+
105
+ def main():
106
+ """Launch distributed processes."""
107
+ args = parse_args()
108
+
109
+ # world size in terms of number of processes
110
+ dist_world_size = args.nproc_per_node * args.nnodes
111
+
112
+ # set PyTorch distributed related environmental variables
113
+ current_env = os.environ.copy()
114
+ current_env["MASTER_ADDR"] = args.master_addr
115
+ current_env["MASTER_PORT"] = str(args.master_port)
116
+ current_env["WORLD_SIZE"] = str(dist_world_size)
117
+
118
+ processes = []
119
+
120
+ if "OMP_NUM_THREADS" not in os.environ and args.nproc_per_node > 1:
121
+ current_env["OMP_NUM_THREADS"] = str(1)
122
+ print(
123
+ "*****************************************\n"
124
+ "Setting OMP_NUM_THREADS environment variable for each process "
125
+ "to be {} in default, to avoid your system being overloaded, "
126
+ "please further tune the variable for optimal performance in "
127
+ "your application as needed. \n"
128
+ "*****************************************".format(
129
+ current_env["OMP_NUM_THREADS"]
130
+ )
131
+ )
132
+
133
+ for local_rank in range(0, args.nproc_per_node):
134
+ # each process's rank
135
+ dist_rank = args.nproc_per_node * args.node_rank + local_rank
136
+ current_env["RANK"] = str(dist_rank)
137
+ current_env["LOCAL_RANK"] = str(local_rank)
138
+
139
+ # spawn the processes
140
+ if args.command:
141
+ cmd = [args.training_script]
142
+ else:
143
+ cmd = [sys.executable, "-u"]
144
+ if args.module:
145
+ cmd.append("-m")
146
+ cmd.append(args.training_script)
147
+
148
+ if not args.use_env:
149
+ cmd.append("--local_rank={}".format(local_rank))
150
+
151
+ cmd.extend(args.training_script_args)
152
+
153
+ process = subprocess.Popen(cmd, env=current_env)
154
+ processes.append(process)
155
+
156
+ for process in processes:
157
+ process.wait()
158
+ if process.returncode != 0:
159
+ raise subprocess.CalledProcessError(returncode=process.returncode, cmd=cmd)
160
+
161
+
162
+ if __name__ == "__main__":
163
+ main()
lscodec/layers/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from .causal_conv import * # NOQA
2
+ from .pqmf import * # NOQA
3
+ from .residual_block import * # NOQA
4
+ from .residual_stack import * # NOQA
5
+ from .tade_res_block import * # NOQA
6
+ from .upsample import * # NOQA
lscodec/layers/causal_conv.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Copyright 2020 Tomoki Hayashi
4
+ # MIT License (https://opensource.org/licenses/MIT)
5
+
6
+ """Causal convolusion layer modules."""
7
+
8
+
9
+ import torch
10
+
11
+
12
+ class CausalConv1d(torch.nn.Module):
13
+ """CausalConv1d module with customized initialization."""
14
+
15
+ def __init__(
16
+ self,
17
+ in_channels,
18
+ out_channels,
19
+ kernel_size,
20
+ dilation=1,
21
+ bias=True,
22
+ pad="ConstantPad1d",
23
+ pad_params={"value": 0.0},
24
+ ):
25
+ """Initialize CausalConv1d module."""
26
+ super(CausalConv1d, self).__init__()
27
+ self.pad = getattr(torch.nn, pad)((kernel_size - 1) * dilation, **pad_params)
28
+ self.conv = torch.nn.Conv1d(
29
+ in_channels, out_channels, kernel_size, dilation=dilation, bias=bias
30
+ )
31
+
32
+ def forward(self, x):
33
+ """Calculate forward propagation.
34
+
35
+ Args:
36
+ x (Tensor): Input tensor (B, in_channels, T).
37
+
38
+ Returns:
39
+ Tensor: Output tensor (B, out_channels, T).
40
+
41
+ """
42
+ return self.conv(self.pad(x))[:, :, : x.size(2)]
43
+
44
+
45
+ class CausalConvTranspose1d(torch.nn.Module):
46
+ """CausalConvTranspose1d module with customized initialization."""
47
+
48
+ def __init__(self, in_channels, out_channels, kernel_size, stride, bias=True):
49
+ """Initialize CausalConvTranspose1d module."""
50
+ super(CausalConvTranspose1d, self).__init__()
51
+ self.deconv = torch.nn.ConvTranspose1d(
52
+ in_channels, out_channels, kernel_size, stride, bias=bias
53
+ )
54
+ self.stride = stride
55
+
56
+ def forward(self, x):
57
+ """Calculate forward propagation.
58
+
59
+ Args:
60
+ x (Tensor): Input tensor (B, in_channels, T_in).
61
+
62
+ Returns:
63
+ Tensor: Output tensor (B, out_channels, T_out).
64
+
65
+ """
66
+ return self.deconv(x)[:, :, : -self.stride]
lscodec/layers/pqmf.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Copyright 2020 Tomoki Hayashi
4
+ # MIT License (https://opensource.org/licenses/MIT)
5
+
6
+ """Pseudo QMF modules."""
7
+
8
+ import numpy as np
9
+ import torch
10
+ import torch.nn.functional as F
11
+
12
+ from scipy.signal import kaiser
13
+
14
+
15
+ def design_prototype_filter(taps=62, cutoff_ratio=0.142, beta=9.0):
16
+ """Design prototype filter for PQMF.
17
+
18
+ This method is based on `A Kaiser window approach for the design of prototype
19
+ filters of cosine modulated filterbanks`_.
20
+
21
+ Args:
22
+ taps (int): The number of filter taps.
23
+ cutoff_ratio (float): Cut-off frequency ratio.
24
+ beta (float): Beta coefficient for kaiser window.
25
+
26
+ Returns:
27
+ ndarray: Impluse response of prototype filter (taps + 1,).
28
+
29
+ .. _`A Kaiser window approach for the design of prototype filters of cosine modulated filterbanks`:
30
+ https://ieeexplore.ieee.org/abstract/document/681427
31
+
32
+ """
33
+ # check the arguments are valid
34
+ assert taps % 2 == 0, "The number of taps mush be even number."
35
+ assert 0.0 < cutoff_ratio < 1.0, "Cutoff ratio must be > 0.0 and < 1.0."
36
+
37
+ # make initial filter
38
+ omega_c = np.pi * cutoff_ratio
39
+ with np.errstate(invalid="ignore"):
40
+ h_i = np.sin(omega_c * (np.arange(taps + 1) - 0.5 * taps)) / (
41
+ np.pi * (np.arange(taps + 1) - 0.5 * taps)
42
+ )
43
+ h_i[taps // 2] = np.cos(0) * cutoff_ratio # fix nan due to indeterminate form
44
+
45
+ # apply kaiser window
46
+ w = kaiser(taps + 1, beta)
47
+ h = h_i * w
48
+
49
+ return h
50
+
51
+
52
+ class PQMF(torch.nn.Module):
53
+ """PQMF module.
54
+
55
+ This module is based on `Near-perfect-reconstruction pseudo-QMF banks`_.
56
+
57
+ .. _`Near-perfect-reconstruction pseudo-QMF banks`:
58
+ https://ieeexplore.ieee.org/document/258122
59
+
60
+ """
61
+
62
+ def __init__(self, subbands=4, taps=62, cutoff_ratio=0.142, beta=9.0):
63
+ """Initilize PQMF module.
64
+
65
+ The cutoff_ratio and beta parameters are optimized for #subbands = 4.
66
+ See dicussion in https://github.com/kan-bayashi/ParallelWaveGAN/issues/195.
67
+
68
+ Args:
69
+ subbands (int): The number of subbands.
70
+ taps (int): The number of filter taps.
71
+ cutoff_ratio (float): Cut-off frequency ratio.
72
+ beta (float): Beta coefficient for kaiser window.
73
+
74
+ """
75
+ super(PQMF, self).__init__()
76
+
77
+ # build analysis & synthesis filter coefficients
78
+ h_proto = design_prototype_filter(taps, cutoff_ratio, beta)
79
+ h_analysis = np.zeros((subbands, len(h_proto)))
80
+ h_synthesis = np.zeros((subbands, len(h_proto)))
81
+ for k in range(subbands):
82
+ h_analysis[k] = (
83
+ 2
84
+ * h_proto
85
+ * np.cos(
86
+ (2 * k + 1)
87
+ * (np.pi / (2 * subbands))
88
+ * (np.arange(taps + 1) - (taps / 2))
89
+ + (-1) ** k * np.pi / 4
90
+ )
91
+ )
92
+ h_synthesis[k] = (
93
+ 2
94
+ * h_proto
95
+ * np.cos(
96
+ (2 * k + 1)
97
+ * (np.pi / (2 * subbands))
98
+ * (np.arange(taps + 1) - (taps / 2))
99
+ - (-1) ** k * np.pi / 4
100
+ )
101
+ )
102
+
103
+ # convert to tensor
104
+ analysis_filter = torch.from_numpy(h_analysis).float().unsqueeze(1)
105
+ synthesis_filter = torch.from_numpy(h_synthesis).float().unsqueeze(0)
106
+
107
+ # register coefficients as beffer
108
+ self.register_buffer("analysis_filter", analysis_filter)
109
+ self.register_buffer("synthesis_filter", synthesis_filter)
110
+
111
+ # filter for downsampling & upsampling
112
+ updown_filter = torch.zeros((subbands, subbands, subbands)).float()
113
+ for k in range(subbands):
114
+ updown_filter[k, k, 0] = 1.0
115
+ self.register_buffer("updown_filter", updown_filter)
116
+ self.subbands = subbands
117
+
118
+ # keep padding info
119
+ self.pad_fn = torch.nn.ConstantPad1d(taps // 2, 0.0)
120
+
121
+ def analysis(self, x):
122
+ """Analysis with PQMF.
123
+
124
+ Args:
125
+ x (Tensor): Input tensor (B, 1, T).
126
+
127
+ Returns:
128
+ Tensor: Output tensor (B, subbands, T // subbands).
129
+
130
+ """
131
+ x = F.conv1d(self.pad_fn(x), self.analysis_filter)
132
+ return F.conv1d(x, self.updown_filter, stride=self.subbands)
133
+
134
+ def synthesis(self, x):
135
+ """Synthesis with PQMF.
136
+
137
+ Args:
138
+ x (Tensor): Input tensor (B, subbands, T // subbands).
139
+
140
+ Returns:
141
+ Tensor: Output tensor (B, 1, T).
142
+
143
+ """
144
+ # NOTE(kan-bayashi): Power will be dreased so here multipy by # subbands.
145
+ # Not sure this is the correct way, it is better to check again.
146
+ # TODO(kan-bayashi): Understand the reconstruction procedure
147
+ x = F.conv_transpose1d(
148
+ x, self.updown_filter * self.subbands, stride=self.subbands
149
+ )
150
+ return F.conv1d(self.pad_fn(x), self.synthesis_filter)
lscodec/layers/residual_block.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Residual block modules.
4
+
5
+ References:
6
+ - https://github.com/r9y9/wavenet_vocoder
7
+ - https://github.com/jik876/hifi-gan
8
+
9
+ """
10
+
11
+ import math
12
+
13
+ import torch
14
+ import torch.nn.functional as F
15
+
16
+
17
+ class Conv1d(torch.nn.Conv1d):
18
+ """Conv1d module with customized initialization."""
19
+
20
+ def __init__(self, *args, **kwargs):
21
+ """Initialize Conv1d module."""
22
+ super(Conv1d, self).__init__(*args, **kwargs)
23
+
24
+ def reset_parameters(self):
25
+ """Reset parameters."""
26
+ torch.nn.init.kaiming_normal_(self.weight, nonlinearity="relu")
27
+ if self.bias is not None:
28
+ torch.nn.init.constant_(self.bias, 0.0)
29
+
30
+
31
+ class Conv1d1x1(Conv1d):
32
+ """1x1 Conv1d with customized initialization."""
33
+
34
+ def __init__(self, in_channels, out_channels, bias):
35
+ """Initialize 1x1 Conv1d module."""
36
+ super(Conv1d1x1, self).__init__(
37
+ in_channels, out_channels, kernel_size=1, padding=0, dilation=1, bias=bias
38
+ )
39
+
40
+
41
+ class WaveNetResidualBlock(torch.nn.Module):
42
+ """Residual block module in WaveNet."""
43
+
44
+ def __init__(
45
+ self,
46
+ kernel_size=3,
47
+ residual_channels=64,
48
+ gate_channels=128,
49
+ skip_channels=64,
50
+ aux_channels=80,
51
+ dropout=0.0,
52
+ dilation=1,
53
+ bias=True,
54
+ use_causal_conv=False,
55
+ ):
56
+ """Initialize WaveNetResidualBlock module.
57
+
58
+ Args:
59
+ kernel_size (int): Kernel size of dilation convolution layer.
60
+ residual_channels (int): Number of channels for residual connection.
61
+ skip_channels (int): Number of channels for skip connection.
62
+ aux_channels (int): Local conditioning channels i.e. auxiliary input dimension.
63
+ dropout (float): Dropout probability.
64
+ dilation (int): Dilation factor.
65
+ bias (bool): Whether to add bias parameter in convolution layers.
66
+ use_causal_conv (bool): Whether to use use_causal_conv or non-use_causal_conv convolution.
67
+
68
+ """
69
+ super().__init__()
70
+ self.dropout = dropout
71
+ # no future time stamps available
72
+ if use_causal_conv:
73
+ padding = (kernel_size - 1) * dilation
74
+ else:
75
+ assert (kernel_size - 1) % 2 == 0, "Not support even number kernel size."
76
+ padding = (kernel_size - 1) // 2 * dilation
77
+ self.use_causal_conv = use_causal_conv
78
+
79
+ # dilation conv
80
+ self.conv = Conv1d(
81
+ residual_channels,
82
+ gate_channels,
83
+ kernel_size,
84
+ padding=padding,
85
+ dilation=dilation,
86
+ bias=bias,
87
+ )
88
+
89
+ # local conditioning
90
+ if aux_channels > 0:
91
+ self.conv1x1_aux = Conv1d1x1(aux_channels, gate_channels, bias=False)
92
+ else:
93
+ self.conv1x1_aux = None
94
+
95
+ # conv output is split into two groups
96
+ gate_out_channels = gate_channels // 2
97
+ self.conv1x1_out = Conv1d1x1(gate_out_channels, residual_channels, bias=bias)
98
+ self.conv1x1_skip = Conv1d1x1(gate_out_channels, skip_channels, bias=bias)
99
+
100
+ def forward(self, x, c):
101
+ """Calculate forward propagation.
102
+
103
+ Args:
104
+ x (Tensor): Input tensor (B, residual_channels, T).
105
+ c (Tensor): Local conditioning auxiliary tensor (B, aux_channels, T).
106
+
107
+ Returns:
108
+ Tensor: Output tensor for residual connection (B, residual_channels, T).
109
+ Tensor: Output tensor for skip connection (B, skip_channels, T).
110
+
111
+ """
112
+ residual = x
113
+ x = F.dropout(x, p=self.dropout, training=self.training)
114
+ x = self.conv(x)
115
+
116
+ # remove future time steps if use_causal_conv conv
117
+ x = x[:, :, : residual.size(-1)] if self.use_causal_conv else x
118
+
119
+ # split into two part for gated activation
120
+ splitdim = 1
121
+ xa, xb = x.split(x.size(splitdim) // 2, dim=splitdim)
122
+
123
+ # local conditioning
124
+ if c is not None:
125
+ assert self.conv1x1_aux is not None
126
+ c = self.conv1x1_aux(c)
127
+ ca, cb = c.split(c.size(splitdim) // 2, dim=splitdim)
128
+ xa, xb = xa + ca, xb + cb
129
+
130
+ x = torch.tanh(xa) * torch.sigmoid(xb)
131
+
132
+ # for skip connection
133
+ s = self.conv1x1_skip(x)
134
+
135
+ # for residual connection
136
+ x = (self.conv1x1_out(x) + residual) * math.sqrt(0.5)
137
+
138
+ return x, s
139
+
140
+
141
+ class HiFiGANResidualBlock(torch.nn.Module):
142
+ """Residual block module in HiFiGAN."""
143
+
144
+ def __init__(
145
+ self,
146
+ kernel_size=3,
147
+ channels=512,
148
+ dilations=(1, 3, 5),
149
+ bias=True,
150
+ use_additional_convs=True,
151
+ nonlinear_activation="LeakyReLU",
152
+ nonlinear_activation_params={"negative_slope": 0.1},
153
+ ):
154
+ """Initialize HiFiGANResidualBlock module.
155
+
156
+ Args:
157
+ kernel_size (int): Kernel size of dilation convolution layer.
158
+ channels (int): Number of channels for convolution layer.
159
+ dilations (List[int]): List of dilation factors.
160
+ use_additional_convs (bool): Whether to use additional convolution layers.
161
+ bias (bool): Whether to add bias parameter in convolution layers.
162
+ nonlinear_activation (str): Activation function module name.
163
+ nonlinear_activation_params (dict): Hyperparameters for activation function.
164
+
165
+ """
166
+ super().__init__()
167
+ self.use_additional_convs = use_additional_convs
168
+ self.convs1 = torch.nn.ModuleList()
169
+ if use_additional_convs:
170
+ self.convs2 = torch.nn.ModuleList()
171
+ assert kernel_size % 2 == 1, "Kernel size must be odd number."
172
+ for dilation in dilations:
173
+ self.convs1 += [
174
+ torch.nn.Sequential(
175
+ getattr(torch.nn, nonlinear_activation)(
176
+ **nonlinear_activation_params
177
+ ),
178
+ torch.nn.Conv1d(
179
+ channels,
180
+ channels,
181
+ kernel_size,
182
+ 1,
183
+ dilation=dilation,
184
+ bias=bias,
185
+ padding=(kernel_size - 1) // 2 * dilation,
186
+ ),
187
+ )
188
+ ]
189
+ if use_additional_convs:
190
+ self.convs2 += [
191
+ torch.nn.Sequential(
192
+ getattr(torch.nn, nonlinear_activation)(
193
+ **nonlinear_activation_params
194
+ ),
195
+ torch.nn.Conv1d(
196
+ channels,
197
+ channels,
198
+ kernel_size,
199
+ 1,
200
+ dilation=1,
201
+ bias=bias,
202
+ padding=(kernel_size - 1) // 2,
203
+ ),
204
+ )
205
+ ]
206
+
207
+ def forward(self, x):
208
+ """Calculate forward propagation.
209
+
210
+ Args:
211
+ x (Tensor): Input tensor (B, channels, T).
212
+
213
+ Returns:
214
+ Tensor: Output tensor (B, channels, T).
215
+
216
+ """
217
+ for idx in range(len(self.convs1)):
218
+ xt = self.convs1[idx](x)
219
+ if self.use_additional_convs:
220
+ xt = self.convs2[idx](xt)
221
+ x = xt + x
222
+ return x
lscodec/layers/residual_stack.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Copyright 2020 Tomoki Hayashi
4
+ # MIT License (https://opensource.org/licenses/MIT)
5
+
6
+ """Residual stack module in MelGAN."""
7
+
8
+ import torch
9
+
10
+ from lscodec.layers import CausalConv1d
11
+
12
+
13
+ class ResidualStack(torch.nn.Module):
14
+ """Residual stack module introduced in MelGAN."""
15
+
16
+ def __init__(
17
+ self,
18
+ kernel_size=3,
19
+ channels=32,
20
+ dilation=1,
21
+ bias=True,
22
+ nonlinear_activation="LeakyReLU",
23
+ nonlinear_activation_params={"negative_slope": 0.2},
24
+ pad="ReflectionPad1d",
25
+ pad_params={},
26
+ use_causal_conv=False,
27
+ ):
28
+ """Initialize ResidualStack module.
29
+
30
+ Args:
31
+ kernel_size (int): Kernel size of dilation convolution layer.
32
+ channels (int): Number of channels of convolution layers.
33
+ dilation (int): Dilation factor.
34
+ bias (bool): Whether to add bias parameter in convolution layers.
35
+ nonlinear_activation (str): Activation function module name.
36
+ nonlinear_activation_params (dict): Hyperparameters for activation function.
37
+ pad (str): Padding function module name before dilated convolution layer.
38
+ pad_params (dict): Hyperparameters for padding function.
39
+ use_causal_conv (bool): Whether to use causal convolution.
40
+
41
+ """
42
+ super(ResidualStack, self).__init__()
43
+
44
+ # defile residual stack part
45
+ if not use_causal_conv:
46
+ assert (kernel_size - 1) % 2 == 0, "Not support even number kernel size."
47
+ self.stack = torch.nn.Sequential(
48
+ getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
49
+ getattr(torch.nn, pad)((kernel_size - 1) // 2 * dilation, **pad_params),
50
+ torch.nn.Conv1d(
51
+ channels, channels, kernel_size, dilation=dilation, bias=bias
52
+ ),
53
+ getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
54
+ torch.nn.Conv1d(channels, channels, 1, bias=bias),
55
+ )
56
+ else:
57
+ self.stack = torch.nn.Sequential(
58
+ getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
59
+ CausalConv1d(
60
+ channels,
61
+ channels,
62
+ kernel_size,
63
+ dilation=dilation,
64
+ bias=bias,
65
+ pad=pad,
66
+ pad_params=pad_params,
67
+ ),
68
+ getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
69
+ torch.nn.Conv1d(channels, channels, 1, bias=bias),
70
+ )
71
+
72
+ # defile extra layer for skip connection
73
+ self.skip_layer = torch.nn.Conv1d(channels, channels, 1, bias=bias)
74
+
75
+ def forward(self, c):
76
+ """Calculate forward propagation.
77
+
78
+ Args:
79
+ c (Tensor): Input tensor (B, channels, T).
80
+
81
+ Returns:
82
+ Tensor: Output tensor (B, chennels, T).
83
+
84
+ """
85
+ return self.stack(c) + self.skip_layer(c)
lscodec/layers/tade_res_block.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2021 Tomoki Hayashi
2
+ # MIT License (https://opensource.org/licenses/MIT)
3
+
4
+ """StyleMelGAN's TADEResBlock Modules."""
5
+
6
+ from functools import partial
7
+
8
+ import torch
9
+
10
+
11
+ class TADELayer(torch.nn.Module):
12
+ """TADE Layer module."""
13
+
14
+ def __init__(
15
+ self,
16
+ in_channels=64,
17
+ aux_channels=80,
18
+ kernel_size=9,
19
+ bias=True,
20
+ upsample_factor=2,
21
+ upsample_mode="nearest",
22
+ ):
23
+ """Initilize TADE layer."""
24
+ super().__init__()
25
+ self.norm = torch.nn.InstanceNorm1d(in_channels)
26
+ self.aux_conv = torch.nn.Sequential(
27
+ torch.nn.Conv1d(
28
+ aux_channels,
29
+ in_channels,
30
+ kernel_size,
31
+ 1,
32
+ bias=bias,
33
+ padding=(kernel_size - 1) // 2,
34
+ ),
35
+ # NOTE(kan-bayashi): Use non-linear activation?
36
+ )
37
+ self.gated_conv = torch.nn.Sequential(
38
+ torch.nn.Conv1d(
39
+ in_channels,
40
+ in_channels * 2,
41
+ kernel_size,
42
+ 1,
43
+ bias=bias,
44
+ padding=(kernel_size - 1) // 2,
45
+ ),
46
+ # NOTE(kan-bayashi): Use non-linear activation?
47
+ )
48
+ self.upsample = torch.nn.Upsample(
49
+ scale_factor=upsample_factor, mode=upsample_mode
50
+ )
51
+
52
+ def forward(self, x, c):
53
+ """Calculate forward propagation.
54
+
55
+ Args:
56
+ x (Tensor): Input tensor (B, in_channels, T).
57
+ c (Tensor): Auxiliary input tensor (B, aux_channels, T').
58
+
59
+ Returns:
60
+ Tensor: Output tensor (B, in_channels, T * in_upsample_factor).
61
+ Tensor: Upsampled aux tensor (B, in_channels, T * aux_upsample_factor).
62
+
63
+ """
64
+ x = self.norm(x)
65
+ c = self.upsample(c)
66
+ c = self.aux_conv(c)
67
+ cg = self.gated_conv(c)
68
+ cg1, cg2 = cg.split(cg.size(1) // 2, dim=1)
69
+ # NOTE(kan-bayashi): Use upsample for noise input here?
70
+ y = cg1 * self.upsample(x) + cg2
71
+ # NOTE(kan-bayashi): Return upsampled aux here?
72
+ return y, c
73
+
74
+
75
+ class TADEResBlock(torch.nn.Module):
76
+ """TADEResBlock module."""
77
+
78
+ def __init__(
79
+ self,
80
+ in_channels=64,
81
+ aux_channels=80,
82
+ kernel_size=9,
83
+ dilation=2,
84
+ bias=True,
85
+ upsample_factor=2,
86
+ upsample_mode="nearest",
87
+ gated_function="softmax",
88
+ ):
89
+ """Initialize TADEResBlock module."""
90
+ super().__init__()
91
+ self.tade1 = TADELayer(
92
+ in_channels=in_channels,
93
+ aux_channels=aux_channels,
94
+ kernel_size=kernel_size,
95
+ bias=bias,
96
+ # NOTE(kan-bayashi): Use upsample in the first TADE layer?
97
+ upsample_factor=1,
98
+ upsample_mode=upsample_mode,
99
+ )
100
+ self.gated_conv1 = torch.nn.Conv1d(
101
+ in_channels,
102
+ in_channels * 2,
103
+ kernel_size,
104
+ 1,
105
+ bias=bias,
106
+ padding=(kernel_size - 1) // 2,
107
+ )
108
+ self.tade2 = TADELayer(
109
+ in_channels=in_channels,
110
+ aux_channels=in_channels,
111
+ kernel_size=kernel_size,
112
+ bias=bias,
113
+ upsample_factor=upsample_factor,
114
+ upsample_mode=upsample_mode,
115
+ )
116
+ self.gated_conv2 = torch.nn.Conv1d(
117
+ in_channels,
118
+ in_channels * 2,
119
+ kernel_size,
120
+ 1,
121
+ bias=bias,
122
+ dilation=dilation,
123
+ padding=(kernel_size - 1) // 2 * dilation,
124
+ )
125
+ self.upsample = torch.nn.Upsample(
126
+ scale_factor=upsample_factor, mode=upsample_mode
127
+ )
128
+ if gated_function == "softmax":
129
+ self.gated_function = partial(torch.softmax, dim=1)
130
+ elif gated_function == "sigmoid":
131
+ self.gated_function = torch.sigmoid
132
+ else:
133
+ raise ValueError(f"{gated_function} is not supported.")
134
+
135
+ def forward(self, x, c):
136
+ """Calculate forward propagation.
137
+
138
+ Args:
139
+ x (Tensor): Input tensor (B, in_channels, T).
140
+ c (Tensor): Auxiliary input tensor (B, aux_channels, T').
141
+
142
+ Returns:
143
+ Tensor: Output tensor (B, in_channels, T * in_upsample_factor).
144
+ Tensor: Upsampled auxirialy tensor (B, in_channels, T * in_upsample_factor).
145
+
146
+ """
147
+ residual = x
148
+
149
+ x, c = self.tade1(x, c)
150
+ x = self.gated_conv1(x)
151
+ xa, xb = x.split(x.size(1) // 2, dim=1)
152
+ x = self.gated_function(xa) * torch.tanh(xb)
153
+
154
+ x, c = self.tade2(x, c)
155
+ x = self.gated_conv2(x)
156
+ xa, xb = x.split(x.size(1) // 2, dim=1)
157
+ x = self.gated_function(xa) * torch.tanh(xb)
158
+
159
+ # NOTE(kan-bayashi): Return upsampled aux here?
160
+ return self.upsample(residual) + x, c
lscodec/layers/upsample.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Upsampling module.
4
+
5
+ This code is modified from https://github.com/r9y9/wavenet_vocoder.
6
+
7
+ """
8
+
9
+ import numpy as np
10
+ import torch
11
+ import torch.nn.functional as F
12
+
13
+ from lscodec.layers import Conv1d
14
+
15
+
16
+ class Stretch2d(torch.nn.Module):
17
+ """Stretch2d module."""
18
+
19
+ def __init__(self, x_scale, y_scale, mode="nearest"):
20
+ """Initialize Stretch2d module.
21
+
22
+ Args:
23
+ x_scale (int): X scaling factor (Time axis in spectrogram).
24
+ y_scale (int): Y scaling factor (Frequency axis in spectrogram).
25
+ mode (str): Interpolation mode.
26
+
27
+ """
28
+ super(Stretch2d, self).__init__()
29
+ self.x_scale = x_scale
30
+ self.y_scale = y_scale
31
+ self.mode = mode
32
+
33
+ def forward(self, x):
34
+ """Calculate forward propagation.
35
+
36
+ Args:
37
+ x (Tensor): Input tensor (B, C, F, T).
38
+
39
+ Returns:
40
+ Tensor: Interpolated tensor (B, C, F * y_scale, T * x_scale),
41
+
42
+ """
43
+ return F.interpolate(
44
+ x, scale_factor=(self.y_scale, self.x_scale), mode=self.mode
45
+ )
46
+
47
+
48
+ class Conv2d(torch.nn.Conv2d):
49
+ """Conv2d module with customized initialization."""
50
+
51
+ def __init__(self, *args, **kwargs):
52
+ """Initialize Conv2d module."""
53
+ super(Conv2d, self).__init__(*args, **kwargs)
54
+
55
+ def reset_parameters(self):
56
+ """Reset parameters."""
57
+ self.weight.data.fill_(1.0 / np.prod(self.kernel_size))
58
+ if self.bias is not None:
59
+ torch.nn.init.constant_(self.bias, 0.0)
60
+
61
+
62
+ class UpsampleNetwork(torch.nn.Module):
63
+ """Upsampling network module."""
64
+
65
+ def __init__(
66
+ self,
67
+ upsample_scales,
68
+ nonlinear_activation=None,
69
+ nonlinear_activation_params={},
70
+ interpolate_mode="nearest",
71
+ freq_axis_kernel_size=1,
72
+ use_causal_conv=False,
73
+ ):
74
+ """Initialize upsampling network module.
75
+
76
+ Args:
77
+ upsample_scales (list): List of upsampling scales.
78
+ nonlinear_activation (str): Activation function name.
79
+ nonlinear_activation_params (dict): Arguments for specified activation function.
80
+ interpolate_mode (str): Interpolation mode.
81
+ freq_axis_kernel_size (int): Kernel size in the direction of frequency axis.
82
+
83
+ """
84
+ super(UpsampleNetwork, self).__init__()
85
+ self.use_causal_conv = use_causal_conv
86
+ self.up_layers = torch.nn.ModuleList()
87
+ for scale in upsample_scales:
88
+ # interpolation layer
89
+ stretch = Stretch2d(scale, 1, interpolate_mode)
90
+ self.up_layers += [stretch]
91
+
92
+ # conv layer
93
+ assert (
94
+ freq_axis_kernel_size - 1
95
+ ) % 2 == 0, "Not support even number freq axis kernel size."
96
+ freq_axis_padding = (freq_axis_kernel_size - 1) // 2
97
+ kernel_size = (freq_axis_kernel_size, scale * 2 + 1)
98
+ if use_causal_conv:
99
+ padding = (freq_axis_padding, scale * 2)
100
+ else:
101
+ padding = (freq_axis_padding, scale)
102
+ conv = Conv2d(1, 1, kernel_size=kernel_size, padding=padding, bias=False)
103
+ self.up_layers += [conv]
104
+
105
+ # nonlinear
106
+ if nonlinear_activation is not None:
107
+ nonlinear = getattr(torch.nn, nonlinear_activation)(
108
+ **nonlinear_activation_params
109
+ )
110
+ self.up_layers += [nonlinear]
111
+
112
+ def forward(self, c):
113
+ """Calculate forward propagation.
114
+
115
+ Args:
116
+ c : Input tensor (B, C, T).
117
+
118
+ Returns:
119
+ Tensor: Upsampled tensor (B, C, T'), where T' = T * prod(upsample_scales).
120
+
121
+ """
122
+ c = c.unsqueeze(1) # (B, 1, C, T)
123
+ for f in self.up_layers:
124
+ if self.use_causal_conv and isinstance(f, Conv2d):
125
+ c = f(c)[..., : c.size(-1)]
126
+ else:
127
+ c = f(c)
128
+ return c.squeeze(1) # (B, C, T')
129
+
130
+
131
+ class ConvInUpsampleNetwork(torch.nn.Module):
132
+ """Convolution + upsampling network module."""
133
+
134
+ def __init__(
135
+ self,
136
+ upsample_scales,
137
+ nonlinear_activation=None,
138
+ nonlinear_activation_params={},
139
+ interpolate_mode="nearest",
140
+ freq_axis_kernel_size=1,
141
+ aux_channels=80,
142
+ aux_context_window=0,
143
+ use_causal_conv=False,
144
+ ):
145
+ """Initialize convolution + upsampling network module.
146
+
147
+ Args:
148
+ upsample_scales (list): List of upsampling scales.
149
+ nonlinear_activation (str): Activation function name.
150
+ nonlinear_activation_params (dict): Arguments for specified activation function.
151
+ mode (str): Interpolation mode.
152
+ freq_axis_kernel_size (int): Kernel size in the direction of frequency axis.
153
+ aux_channels (int): Number of channels of pre-convolutional layer.
154
+ aux_context_window (int): Context window size of the pre-convolutional layer.
155
+ use_causal_conv (bool): Whether to use causal structure.
156
+
157
+ """
158
+ super(ConvInUpsampleNetwork, self).__init__()
159
+ self.aux_context_window = aux_context_window
160
+ self.use_causal_conv = use_causal_conv and aux_context_window > 0
161
+ # To capture wide-context information in conditional features
162
+ kernel_size = (
163
+ aux_context_window + 1 if use_causal_conv else 2 * aux_context_window + 1
164
+ )
165
+ # NOTE(kan-bayashi): Here do not use padding because the input is already padded
166
+ self.conv_in = Conv1d(
167
+ aux_channels, aux_channels, kernel_size=kernel_size, bias=False
168
+ )
169
+ self.upsample = UpsampleNetwork(
170
+ upsample_scales=upsample_scales,
171
+ nonlinear_activation=nonlinear_activation,
172
+ nonlinear_activation_params=nonlinear_activation_params,
173
+ interpolate_mode=interpolate_mode,
174
+ freq_axis_kernel_size=freq_axis_kernel_size,
175
+ use_causal_conv=use_causal_conv,
176
+ )
177
+
178
+ def forward(self, c):
179
+ """Calculate forward propagation.
180
+
181
+ Args:
182
+ c : Input tensor (B, C, T').
183
+
184
+ Returns:
185
+ Tensor: Upsampled tensor (B, C, T),
186
+ where T = (T' - aux_context_window * 2) * prod(upsample_scales).
187
+
188
+ Note:
189
+ The length of inputs considers the context window size.
190
+
191
+ """
192
+ c_ = self.conv_in(c)
193
+ c = c_[:, :, : -self.aux_context_window] if self.use_causal_conv else c_
194
+ return self.upsample(c)
lscodec/models/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .hifigan import * # NOQA
2
+ from .melgan import * # NOQA
3
+ from .lscodec import *
lscodec/models/conformer/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Initialize sub package."""
lscodec/models/conformer/argument.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Hirofumi Inaguma
2
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3
+
4
+ """Conformer common arguments."""
5
+
6
+
7
+ from distutils.util import strtobool
8
+ import logging
9
+
10
+
11
+ def add_arguments_conformer_common(group):
12
+ """Add Transformer common arguments."""
13
+ group.add_argument(
14
+ "--transformer-encoder-pos-enc-layer-type",
15
+ type=str,
16
+ default="abs_pos",
17
+ choices=["abs_pos", "scaled_abs_pos", "rel_pos"],
18
+ help="Transformer encoder positional encoding layer type",
19
+ )
20
+ group.add_argument(
21
+ "--transformer-encoder-activation-type",
22
+ type=str,
23
+ default="swish",
24
+ choices=["relu", "hardtanh", "selu", "swish"],
25
+ help="Transformer encoder activation function type",
26
+ )
27
+ group.add_argument(
28
+ "--macaron-style",
29
+ default=False,
30
+ type=strtobool,
31
+ help="Whether to use macaron style for positionwise layer",
32
+ )
33
+ # Attention
34
+ group.add_argument(
35
+ "--zero-triu",
36
+ default=False,
37
+ type=strtobool,
38
+ help="If true, zero the uppper triangular part of attention matrix.",
39
+ )
40
+ # Relative positional encoding
41
+ group.add_argument(
42
+ "--rel-pos-type",
43
+ type=str,
44
+ default="legacy",
45
+ choices=["legacy", "latest"],
46
+ help="Whether to use the latest relative positional encoding or the legacy one."
47
+ "The legacy relative positional encoding will be deprecated in the future."
48
+ "More Details can be found in https://github.com/espnet/espnet/pull/2816.",
49
+ )
50
+ # CNN module
51
+ group.add_argument(
52
+ "--use-cnn-module",
53
+ default=False,
54
+ type=strtobool,
55
+ help="Use convolution module or not",
56
+ )
57
+ group.add_argument(
58
+ "--cnn-module-kernel",
59
+ default=31,
60
+ type=int,
61
+ help="Kernel size of convolution module.",
62
+ )
63
+ return group
64
+
65
+
66
+ def verify_rel_pos_type(args):
67
+ """Verify the relative positional encoding type for compatibility.
68
+
69
+ Args:
70
+ args (Namespace): original arguments
71
+ Returns:
72
+ args (Namespace): modified arguments
73
+ """
74
+ rel_pos_type = getattr(args, "rel_pos_type", None)
75
+ if rel_pos_type is None or rel_pos_type == "legacy":
76
+ if args.transformer_encoder_pos_enc_layer_type == "rel_pos":
77
+ args.transformer_encoder_pos_enc_layer_type = "legacy_rel_pos"
78
+ logging.warning(
79
+ "Using legacy_rel_pos and it will be deprecated in the future."
80
+ )
81
+ if args.transformer_encoder_selfattn_layer_type == "rel_selfattn":
82
+ args.transformer_encoder_selfattn_layer_type = "legacy_rel_selfattn"
83
+ logging.warning(
84
+ "Using legacy_rel_selfattn and it will be deprecated in the future."
85
+ )
86
+
87
+ return args
lscodec/models/conformer/attention.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Shigeki Karita
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+ """Multi-Head Attention layer definition."""
8
+
9
+ import math
10
+
11
+ import numpy
12
+ import torch
13
+ from torch import nn
14
+
15
+
16
+ class MultiHeadedAttention(nn.Module):
17
+ """Multi-Head Attention layer.
18
+
19
+ Args:
20
+ n_head (int): The number of heads.
21
+ n_feat (int): The number of features.
22
+ dropout_rate (float): Dropout rate.
23
+
24
+ """
25
+
26
+ def __init__(self, n_head, n_feat, dropout_rate):
27
+ """Construct an MultiHeadedAttention object."""
28
+ super(MultiHeadedAttention, self).__init__()
29
+ assert n_feat % n_head == 0
30
+ # We assume d_v always equals d_k
31
+ self.d_k = n_feat // n_head
32
+ self.h = n_head
33
+ self.linear_q = nn.Linear(n_feat, n_feat)
34
+ self.linear_k = nn.Linear(n_feat, n_feat)
35
+ self.linear_v = nn.Linear(n_feat, n_feat)
36
+ self.linear_out = nn.Linear(n_feat, n_feat)
37
+ self.attn = None
38
+ self.dropout = nn.Dropout(p=dropout_rate)
39
+
40
+ def forward_qkv(self, query, key, value):
41
+ """Transform query, key and value.
42
+
43
+ Args:
44
+ query (torch.Tensor): Query tensor (#batch, time1, size).
45
+ key (torch.Tensor): Key tensor (#batch, time2, size).
46
+ value (torch.Tensor): Value tensor (#batch, time2, size).
47
+
48
+ Returns:
49
+ torch.Tensor: Transformed query tensor (#batch, n_head, time1, d_k).
50
+ torch.Tensor: Transformed key tensor (#batch, n_head, time2, d_k).
51
+ torch.Tensor: Transformed value tensor (#batch, n_head, time2, d_k).
52
+
53
+ """
54
+ n_batch = query.size(0)
55
+ q = self.linear_q(query).view(n_batch, -1, self.h, self.d_k)
56
+ k = self.linear_k(key).view(n_batch, -1, self.h, self.d_k)
57
+ v = self.linear_v(value).view(n_batch, -1, self.h, self.d_k)
58
+ q = q.transpose(1, 2) # (batch, head, time1, d_k)
59
+ k = k.transpose(1, 2) # (batch, head, time2, d_k)
60
+ v = v.transpose(1, 2) # (batch, head, time2, d_k)
61
+
62
+ return q, k, v
63
+
64
+ def forward_attention(self, value, scores, mask):
65
+ """Compute attention context vector.
66
+
67
+ Args:
68
+ value (torch.Tensor): Transformed value (#batch, n_head, time2, d_k).
69
+ scores (torch.Tensor): Attention score (#batch, n_head, time1, time2).
70
+ mask (torch.Tensor): Mask (#batch, 1, time2) or (#batch, time1, time2).
71
+
72
+ Returns:
73
+ torch.Tensor: Transformed value (#batch, time1, d_model)
74
+ weighted by the attention score (#batch, time1, time2).
75
+
76
+ """
77
+ n_batch = value.size(0)
78
+ if mask is not None:
79
+ mask = mask.unsqueeze(1).eq(0) # (batch, 1, *, time2)
80
+ min_value = float(
81
+ numpy.finfo(torch.tensor(0, dtype=scores.dtype).numpy().dtype).min
82
+ )
83
+ scores = scores.masked_fill(mask, min_value)
84
+ self.attn = torch.softmax(scores, dim=-1).masked_fill(
85
+ mask, 0.0
86
+ ) # (batch, head, time1, time2)
87
+ else:
88
+ self.attn = torch.softmax(scores, dim=-1) # (batch, head, time1, time2)
89
+
90
+ p_attn = self.dropout(self.attn)
91
+ x = torch.matmul(p_attn, value) # (batch, head, time1, d_k)
92
+ x = (
93
+ x.transpose(1, 2).contiguous().view(n_batch, -1, self.h * self.d_k)
94
+ ) # (batch, time1, d_model)
95
+
96
+ return self.linear_out(x) # (batch, time1, d_model)
97
+
98
+ def forward(self, query, key, value, mask):
99
+ """Compute scaled dot product attention.
100
+
101
+ Args:
102
+ query (torch.Tensor): Query tensor (#batch, time1, size).
103
+ key (torch.Tensor): Key tensor (#batch, time2, size).
104
+ value (torch.Tensor): Value tensor (#batch, time2, size).
105
+ mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
106
+ (#batch, time1, time2).
107
+
108
+ Returns:
109
+ torch.Tensor: Output tensor (#batch, time1, d_model).
110
+
111
+ """
112
+ q, k, v = self.forward_qkv(query, key, value)
113
+ scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
114
+ return self.forward_attention(v, scores, mask)
115
+
116
+
117
+ class LegacyRelPositionMultiHeadedAttention(MultiHeadedAttention):
118
+ """Multi-Head Attention layer with relative position encoding (old version).
119
+
120
+ Details can be found in https://github.com/espnet/espnet/pull/2816.
121
+
122
+ Paper: https://arxiv.org/abs/1901.02860
123
+
124
+ Args:
125
+ n_head (int): The number of heads.
126
+ n_feat (int): The number of features.
127
+ dropout_rate (float): Dropout rate.
128
+ zero_triu (bool): Whether to zero the upper triangular part of attention matrix.
129
+
130
+ """
131
+
132
+ def __init__(self, n_head, n_feat, dropout_rate, zero_triu=False):
133
+ """Construct an RelPositionMultiHeadedAttention object."""
134
+ super().__init__(n_head, n_feat, dropout_rate)
135
+ self.zero_triu = zero_triu
136
+ # linear transformation for positional encoding
137
+ self.linear_pos = nn.Linear(n_feat, n_feat, bias=False)
138
+ # these two learnable bias are used in matrix c and matrix d
139
+ # as described in https://arxiv.org/abs/1901.02860 Section 3.3
140
+ self.pos_bias_u = nn.Parameter(torch.Tensor(self.h, self.d_k))
141
+ self.pos_bias_v = nn.Parameter(torch.Tensor(self.h, self.d_k))
142
+ torch.nn.init.xavier_uniform_(self.pos_bias_u)
143
+ torch.nn.init.xavier_uniform_(self.pos_bias_v)
144
+
145
+ def rel_shift(self, x):
146
+ """Compute relative positional encoding.
147
+
148
+ Args:
149
+ x (torch.Tensor): Input tensor (batch, head, time1, time2).
150
+
151
+ Returns:
152
+ torch.Tensor: Output tensor.
153
+
154
+ """
155
+ zero_pad = torch.zeros((*x.size()[:3], 1), device=x.device, dtype=x.dtype)
156
+ x_padded = torch.cat([zero_pad, x], dim=-1)
157
+
158
+ x_padded = x_padded.view(*x.size()[:2], x.size(3) + 1, x.size(2))
159
+ x = x_padded[:, :, 1:].view_as(x)
160
+
161
+ if self.zero_triu:
162
+ ones = torch.ones((x.size(2), x.size(3)))
163
+ x = x * torch.tril(ones, x.size(3) - x.size(2))[None, None, :, :]
164
+
165
+ return x
166
+
167
+ def forward(self, query, key, value, pos_emb, mask):
168
+ """Compute 'Scaled Dot Product Attention' with rel. positional encoding.
169
+
170
+ Args:
171
+ query (torch.Tensor): Query tensor (#batch, time1, size).
172
+ key (torch.Tensor): Key tensor (#batch, time2, size).
173
+ value (torch.Tensor): Value tensor (#batch, time2, size).
174
+ pos_emb (torch.Tensor): Positional embedding tensor (#batch, time1, size).
175
+ mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
176
+ (#batch, time1, time2).
177
+
178
+ Returns:
179
+ torch.Tensor: Output tensor (#batch, time1, d_model).
180
+
181
+ """
182
+ q, k, v = self.forward_qkv(query, key, value)
183
+ q = q.transpose(1, 2) # (batch, time1, head, d_k)
184
+
185
+ n_batch_pos = pos_emb.size(0)
186
+ p = self.linear_pos(pos_emb).view(n_batch_pos, -1, self.h, self.d_k)
187
+ p = p.transpose(1, 2) # (batch, head, time1, d_k)
188
+
189
+ # (batch, head, time1, d_k)
190
+ q_with_bias_u = (q + self.pos_bias_u).transpose(1, 2)
191
+ # (batch, head, time1, d_k)
192
+ q_with_bias_v = (q + self.pos_bias_v).transpose(1, 2)
193
+
194
+ # compute attention score
195
+ # first compute matrix a and matrix c
196
+ # as described in https://arxiv.org/abs/1901.02860 Section 3.3
197
+ # (batch, head, time1, time2)
198
+ matrix_ac = torch.matmul(q_with_bias_u, k.transpose(-2, -1))
199
+
200
+ # compute matrix b and matrix d
201
+ # (batch, head, time1, time1)
202
+ matrix_bd = torch.matmul(q_with_bias_v, p.transpose(-2, -1))
203
+ matrix_bd = self.rel_shift(matrix_bd)
204
+
205
+ scores = (matrix_ac + matrix_bd) / math.sqrt(
206
+ self.d_k
207
+ ) # (batch, head, time1, time2)
208
+
209
+ return self.forward_attention(v, scores, mask)
210
+
211
+
212
+ class RelPositionMultiHeadedAttention(MultiHeadedAttention):
213
+ """Multi-Head Attention layer with relative position encoding (new implementation).
214
+
215
+ Details can be found in https://github.com/espnet/espnet/pull/2816.
216
+
217
+ Paper: https://arxiv.org/abs/1901.02860
218
+
219
+ Args:
220
+ n_head (int): The number of heads.
221
+ n_feat (int): The number of features.
222
+ dropout_rate (float): Dropout rate.
223
+ zero_triu (bool): Whether to zero the upper triangular part of attention matrix.
224
+
225
+ """
226
+
227
+ def __init__(self, n_head, n_feat, dropout_rate, zero_triu=False):
228
+ """Construct an RelPositionMultiHeadedAttention object."""
229
+ super().__init__(n_head, n_feat, dropout_rate)
230
+ self.zero_triu = zero_triu
231
+ # linear transformation for positional encoding
232
+ self.linear_pos = nn.Linear(n_feat, n_feat, bias=False)
233
+ # these two learnable bias are used in matrix c and matrix d
234
+ # as described in https://arxiv.org/abs/1901.02860 Section 3.3
235
+ self.pos_bias_u = nn.Parameter(torch.Tensor(self.h, self.d_k))
236
+ self.pos_bias_v = nn.Parameter(torch.Tensor(self.h, self.d_k))
237
+ torch.nn.init.xavier_uniform_(self.pos_bias_u)
238
+ torch.nn.init.xavier_uniform_(self.pos_bias_v)
239
+
240
+ def rel_shift(self, x):
241
+ """Compute relative positional encoding.
242
+
243
+ Args:
244
+ x (torch.Tensor): Input tensor (batch, head, time1, 2*time1-1).
245
+ time1 means the length of query vector.
246
+
247
+ Returns:
248
+ torch.Tensor: Output tensor.
249
+
250
+ """
251
+ zero_pad = torch.zeros((*x.size()[:3], 1), device=x.device, dtype=x.dtype)
252
+ x_padded = torch.cat([zero_pad, x], dim=-1)
253
+
254
+ x_padded = x_padded.view(*x.size()[:2], x.size(3) + 1, x.size(2))
255
+ x = x_padded[:, :, 1:].view_as(x)[
256
+ :, :, :, : x.size(-1) // 2 + 1
257
+ ] # only keep the positions from 0 to time2
258
+
259
+ if self.zero_triu:
260
+ ones = torch.ones((x.size(2), x.size(3)), device=x.device)
261
+ x = x * torch.tril(ones, x.size(3) - x.size(2))[None, None, :, :]
262
+
263
+ return x
264
+
265
+ def forward(self, query, key, value, pos_emb, mask):
266
+ """Compute 'Scaled Dot Product Attention' with rel. positional encoding.
267
+
268
+ Args:
269
+ query (torch.Tensor): Query tensor (#batch, time1, size).
270
+ key (torch.Tensor): Key tensor (#batch, time2, size).
271
+ value (torch.Tensor): Value tensor (#batch, time2, size).
272
+ pos_emb (torch.Tensor): Positional embedding tensor
273
+ (#batch, 2*time1-1, size).
274
+ mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
275
+ (#batch, time1, time2).
276
+
277
+ Returns:
278
+ torch.Tensor: Output tensor (#batch, time1, d_model).
279
+
280
+ """
281
+ q, k, v = self.forward_qkv(query, key, value)
282
+ q = q.transpose(1, 2) # (batch, time1, head, d_k)
283
+
284
+ n_batch_pos = pos_emb.size(0)
285
+ p = self.linear_pos(pos_emb).view(n_batch_pos, -1, self.h, self.d_k)
286
+ p = p.transpose(1, 2) # (batch, head, 2*time1-1, d_k)
287
+
288
+ # (batch, head, time1, d_k)
289
+ q_with_bias_u = (q + self.pos_bias_u).transpose(1, 2)
290
+ # (batch, head, time1, d_k)
291
+ q_with_bias_v = (q + self.pos_bias_v).transpose(1, 2)
292
+
293
+ # compute attention score
294
+ # first compute matrix a and matrix c
295
+ # as described in https://arxiv.org/abs/1901.02860 Section 3.3
296
+ # (batch, head, time1, time2)
297
+ matrix_ac = torch.matmul(q_with_bias_u, k.transpose(-2, -1))
298
+
299
+ # compute matrix b and matrix d
300
+ # (batch, head, time1, 2*time1-1)
301
+ matrix_bd = torch.matmul(q_with_bias_v, p.transpose(-2, -1))
302
+ matrix_bd = self.rel_shift(matrix_bd)
303
+
304
+ scores = (matrix_ac + matrix_bd) / math.sqrt(
305
+ self.d_k
306
+ ) # (batch, head, time1, time2)
307
+
308
+ return self.forward_attention(v, scores, mask)
lscodec/models/conformer/contextual_block_encoder_layer.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Sat Aug 21 16:57:31 2021.
4
+
5
+ @author: Keqi Deng (UCAS)
6
+ """
7
+
8
+ from espnet.nets.pytorch_backend.transformer.layer_norm import LayerNorm
9
+ import torch
10
+ from torch import nn
11
+
12
+
13
+ class ContextualBlockEncoderLayer(nn.Module):
14
+ """Contexutal Block Encoder layer module.
15
+
16
+ Args:
17
+ size (int): Input dimension.
18
+ self_attn (torch.nn.Module): Self-attention module instance.
19
+ `MultiHeadedAttention` or `RelPositionMultiHeadedAttention` instance
20
+ can be used as the argument.
21
+ feed_forward (torch.nn.Module): Feed-forward module instance.
22
+ `PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
23
+ can be used as the argument.
24
+ feed_forward_macaron (torch.nn.Module): Additional feed-forward module instance.
25
+ `PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
26
+ can be used as the argument.
27
+ conv_module (torch.nn.Module): Convolution module instance.
28
+ `ConvlutionModule` instance can be used as the argument.
29
+ dropout_rate (float): Dropout rate.
30
+ total_layer_num (int): Total number of layers
31
+ normalize_before (bool): Whether to use layer_norm before the first block.
32
+ concat_after (bool): Whether to concat attention layer's input and output.
33
+ if True, additional linear will be applied.
34
+ i.e. x -> x + linear(concat(x, att(x)))
35
+ if False, no additional linear will be applied. i.e. x -> x + att(x)
36
+
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ size,
42
+ self_attn,
43
+ feed_forward,
44
+ feed_forward_macaron,
45
+ conv_module,
46
+ dropout_rate,
47
+ total_layer_num,
48
+ normalize_before=True,
49
+ concat_after=False,
50
+ ):
51
+ """Construct an EncoderLayer object."""
52
+ super(ContextualBlockEncoderLayer, self).__init__()
53
+ self.self_attn = self_attn
54
+ self.feed_forward = feed_forward
55
+ self.feed_forward_macaron = feed_forward_macaron
56
+ self.conv_module = conv_module
57
+ self.norm1 = LayerNorm(size)
58
+ self.norm2 = LayerNorm(size)
59
+ if feed_forward_macaron is not None:
60
+ self.norm_ff_macaron = LayerNorm(size)
61
+ self.ff_scale = 0.5
62
+ else:
63
+ self.ff_scale = 1.0
64
+ if self.conv_module is not None:
65
+ self.norm_conv = LayerNorm(size) # for the CNN module
66
+ self.norm_final = LayerNorm(size) # for the final output of the block
67
+ self.dropout = nn.Dropout(dropout_rate)
68
+ self.size = size
69
+ self.normalize_before = normalize_before
70
+ self.concat_after = concat_after
71
+ self.total_layer_num = total_layer_num
72
+ if self.concat_after:
73
+ self.concat_linear = nn.Linear(size + size, size)
74
+
75
+ def forward(
76
+ self,
77
+ x,
78
+ mask,
79
+ infer_mode=False,
80
+ past_ctx=None,
81
+ next_ctx=None,
82
+ is_short_segment=False,
83
+ layer_idx=0,
84
+ cache=None,
85
+ ):
86
+ """Calculate forward propagation."""
87
+ if self.training or not infer_mode:
88
+ return self.forward_train(x, mask, past_ctx, next_ctx, layer_idx, cache)
89
+ else:
90
+ return self.forward_infer(
91
+ x, mask, past_ctx, next_ctx, is_short_segment, layer_idx, cache
92
+ )
93
+
94
+ def forward_train(
95
+ self, x, mask, past_ctx=None, next_ctx=None, layer_idx=0, cache=None
96
+ ):
97
+ """Compute encoded features.
98
+
99
+ Args:
100
+ x_input (torch.Tensor): Input tensor (#batch, time, size).
101
+ mask (torch.Tensor): Mask tensor for the input (#batch, time).
102
+ past_ctx (torch.Tensor): Previous contexutal vector
103
+ next_ctx (torch.Tensor): Next contexutal vector
104
+ cache (torch.Tensor): Cache tensor of the input (#batch, time - 1, size).
105
+
106
+ Returns:
107
+ torch.Tensor: Output tensor (#batch, time, size).
108
+ torch.Tensor: Mask tensor (#batch, time).
109
+ cur_ctx (torch.Tensor): Current contexutal vector
110
+ next_ctx (torch.Tensor): Next contexutal vector
111
+ layer_idx (int): layer index number
112
+
113
+ """
114
+ nbatch = x.size(0)
115
+ nblock = x.size(1)
116
+
117
+ if past_ctx is not None:
118
+ if next_ctx is None:
119
+ # store all context vectors in one tensor
120
+ next_ctx = past_ctx.new_zeros(
121
+ nbatch, nblock, self.total_layer_num, x.size(-1)
122
+ )
123
+ else:
124
+ x[:, :, 0] = past_ctx[:, :, layer_idx]
125
+
126
+ # reshape ( nbatch, nblock, block_size + 2, dim )
127
+ # -> ( nbatch * nblock, block_size + 2, dim )
128
+ x = x.view(-1, x.size(-2), x.size(-1))
129
+ if mask is not None:
130
+ mask = mask.view(-1, mask.size(-2), mask.size(-1))
131
+
132
+ # whether to use macaron style
133
+ if self.feed_forward_macaron is not None:
134
+ residual = x
135
+ if self.normalize_before:
136
+ x = self.norm_ff_macaron(x)
137
+ x = residual + self.ff_scale * self.dropout(self.feed_forward_macaron(x))
138
+ if not self.normalize_before:
139
+ x = self.norm_ff_macaron(x)
140
+
141
+ residual = x
142
+ if self.normalize_before:
143
+ x = self.norm1(x)
144
+
145
+ if cache is None:
146
+ x_q = x
147
+ else:
148
+ assert cache.shape == (x.shape[0], x.shape[1] - 1, self.size)
149
+ x_q = x[:, -1:, :]
150
+ residual = residual[:, -1:, :]
151
+ mask = None if mask is None else mask[:, -1:, :]
152
+
153
+ if self.concat_after:
154
+ x_concat = torch.cat((x, self.self_attn(x_q, x, x, mask)), dim=-1)
155
+ x = residual + self.concat_linear(x_concat)
156
+ else:
157
+ x = residual + self.dropout(self.self_attn(x_q, x, x, mask))
158
+ if not self.normalize_before:
159
+ x = self.norm1(x)
160
+
161
+ # convolution module
162
+ if self.conv_module is not None:
163
+ residual = x
164
+ if self.normalize_before:
165
+ x = self.norm_conv(x)
166
+ x = residual + self.dropout(self.conv_module(x))
167
+ if not self.normalize_before:
168
+ x = self.norm_conv(x)
169
+
170
+ residual = x
171
+ if self.normalize_before:
172
+ x = self.norm2(x)
173
+ x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
174
+ if not self.normalize_before:
175
+ x = self.norm2(x)
176
+
177
+ if self.conv_module is not None:
178
+ x = self.norm_final(x)
179
+
180
+ if cache is not None:
181
+ x = torch.cat([cache, x], dim=1)
182
+
183
+ layer_idx += 1
184
+ # reshape ( nbatch * nblock, block_size + 2, dim )
185
+ # -> ( nbatch, nblock, block_size + 2, dim )
186
+ x = x.view(nbatch, -1, x.size(-2), x.size(-1)).squeeze(1)
187
+ if mask is not None:
188
+ mask = mask.view(nbatch, -1, mask.size(-2), mask.size(-1)).squeeze(1)
189
+
190
+ if next_ctx is not None and layer_idx < self.total_layer_num:
191
+ next_ctx[:, 0, layer_idx, :] = x[:, 0, -1, :]
192
+ next_ctx[:, 1:, layer_idx, :] = x[:, 0:-1, -1, :]
193
+
194
+ return x, mask, False, next_ctx, next_ctx, layer_idx
195
+
196
+ def forward_infer(
197
+ self,
198
+ x,
199
+ mask,
200
+ past_ctx=None,
201
+ next_ctx=None,
202
+ is_short_segment=False,
203
+ layer_idx=0,
204
+ cache=None,
205
+ ):
206
+ """Compute encoded features.
207
+
208
+ Args:
209
+ x_input (torch.Tensor): Input tensor (#batch, time, size).
210
+ mask (torch.Tensor): Mask tensor for the input (#batch, time).
211
+ past_ctx (torch.Tensor): Previous contexutal vector
212
+ next_ctx (torch.Tensor): Next contexutal vector
213
+ cache (torch.Tensor): Cache tensor of the input (#batch, time - 1, size).
214
+
215
+ Returns:
216
+ torch.Tensor: Output tensor (#batch, time, size).
217
+ torch.Tensor: Mask tensor (#batch, time).
218
+ cur_ctx (torch.Tensor): Current contexutal vector
219
+ next_ctx (torch.Tensor): Next contexutal vector
220
+ layer_idx (int): layer index number
221
+
222
+ """
223
+ nbatch = x.size(0)
224
+ nblock = x.size(1)
225
+ # if layer_idx == 0, next_ctx has to be None
226
+ if layer_idx == 0:
227
+ assert next_ctx is None
228
+ next_ctx = x.new_zeros(nbatch, self.total_layer_num, x.size(-1))
229
+
230
+ # reshape ( nbatch, nblock, block_size + 2, dim )
231
+ # -> ( nbatch * nblock, block_size + 2, dim )
232
+ x = x.view(-1, x.size(-2), x.size(-1))
233
+ if mask is not None:
234
+ mask = mask.view(-1, mask.size(-2), mask.size(-1))
235
+
236
+ # whether to use macaron style
237
+ if self.feed_forward_macaron is not None:
238
+ residual = x
239
+ if self.normalize_before:
240
+ x = self.norm_ff_macaron(x)
241
+ x = residual + self.ff_scale * self.dropout(self.feed_forward_macaron(x))
242
+ if not self.normalize_before:
243
+ x = self.norm_ff_macaron(x)
244
+
245
+ residual = x
246
+ if self.normalize_before:
247
+ x = self.norm1(x)
248
+
249
+ if cache is None:
250
+ x_q = x
251
+ else:
252
+ assert cache.shape == (x.shape[0], x.shape[1] - 1, self.size)
253
+ x_q = x[:, -1:, :]
254
+ residual = residual[:, -1:, :]
255
+ mask = None if mask is None else mask[:, -1:, :]
256
+
257
+ if self.concat_after:
258
+ x_concat = torch.cat((x, self.self_attn(x_q, x, x, mask)), dim=-1)
259
+ x = residual + self.concat_linear(x_concat)
260
+ else:
261
+ x = residual + self.dropout(self.self_attn(x_q, x, x, mask))
262
+ if not self.normalize_before:
263
+ x = self.norm1(x)
264
+
265
+ # convolution module
266
+ if self.conv_module is not None:
267
+ residual = x
268
+ if self.normalize_before:
269
+ x = self.norm_conv(x)
270
+ x = residual + self.dropout(self.conv_module(x))
271
+ if not self.normalize_before:
272
+ x = self.norm_conv(x)
273
+
274
+ residual = x
275
+ if self.normalize_before:
276
+ x = self.norm2(x)
277
+ x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
278
+ if not self.normalize_before:
279
+ x = self.norm2(x)
280
+
281
+ if self.conv_module is not None:
282
+ x = self.norm_final(x)
283
+
284
+ if cache is not None:
285
+ x = torch.cat([cache, x], dim=1)
286
+
287
+ # reshape ( nbatch * nblock, block_size + 2, dim )
288
+ # -> ( nbatch, nblock, block_size + 2, dim )
289
+ x = x.view(nbatch, nblock, x.size(-2), x.size(-1))
290
+ if mask is not None:
291
+ mask = mask.view(nbatch, nblock, mask.size(-2), mask.size(-1))
292
+
293
+ # Propagete context information (the last frame of each block)
294
+ # to the first frame
295
+ # of the next block
296
+
297
+ if not is_short_segment:
298
+ if past_ctx is None:
299
+ # First block of an utterance
300
+ x[:, 0, 0, :] = x[:, 0, -1, :]
301
+ else:
302
+ x[:, 0, 0, :] = past_ctx[:, layer_idx, :]
303
+ if nblock > 1:
304
+ x[:, 1:, 0, :] = x[:, 0:-1, -1, :]
305
+ next_ctx[:, layer_idx, :] = x[:, -1, -1, :]
306
+ else:
307
+ next_ctx = None
308
+
309
+ return x, mask, True, past_ctx, next_ctx, is_short_segment, layer_idx + 1
lscodec/models/conformer/convolution.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2020 Johns Hopkins University (Shinji Watanabe)
5
+ # Northwestern Polytechnical University (Pengcheng Guo)
6
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
7
+
8
+ """ConvolutionModule definition."""
9
+
10
+ from torch import nn
11
+
12
+
13
+ class ConvolutionModule(nn.Module):
14
+ """ConvolutionModule in Conformer model.
15
+
16
+ Args:
17
+ channels (int): The number of channels of conv layers.
18
+ kernel_size (int): Kernerl size of conv layers.
19
+
20
+ """
21
+
22
+ def __init__(self, channels, kernel_size, activation=nn.ReLU(), bias=True):
23
+ """Construct an ConvolutionModule object."""
24
+ super(ConvolutionModule, self).__init__()
25
+ # kernerl_size should be a odd number for 'SAME' padding
26
+ assert (kernel_size - 1) % 2 == 0
27
+
28
+ self.pointwise_conv1 = nn.Conv1d(
29
+ channels,
30
+ 2 * channels,
31
+ kernel_size=1,
32
+ stride=1,
33
+ padding=0,
34
+ bias=bias,
35
+ )
36
+ self.depthwise_conv = nn.Conv1d(
37
+ channels,
38
+ channels,
39
+ kernel_size,
40
+ stride=1,
41
+ padding=(kernel_size - 1) // 2,
42
+ groups=channels,
43
+ bias=bias,
44
+ )
45
+ self.norm = nn.BatchNorm1d(channels)
46
+ self.pointwise_conv2 = nn.Conv1d(
47
+ channels,
48
+ channels,
49
+ kernel_size=1,
50
+ stride=1,
51
+ padding=0,
52
+ bias=bias,
53
+ )
54
+ self.activation = activation
55
+
56
+ def forward(self, x):
57
+ """Compute convolution module.
58
+
59
+ Args:
60
+ x (torch.Tensor): Input tensor (#batch, time, channels).
61
+
62
+ Returns:
63
+ torch.Tensor: Output tensor (#batch, time, channels).
64
+
65
+ """
66
+ # exchange the temporal dimension and the feature dimension
67
+ x = x.transpose(1, 2)
68
+
69
+ # GLU mechanism
70
+ x = self.pointwise_conv1(x) # (batch, 2*channel, dim)
71
+ x = nn.functional.glu(x, dim=1) # (batch, channel, dim)
72
+
73
+ # 1D Depthwise Conv
74
+ x = self.depthwise_conv(x)
75
+ x = self.activation(self.norm(x))
76
+
77
+ x = self.pointwise_conv2(x)
78
+
79
+ return x.transpose(1, 2)
lscodec/models/conformer/decoder.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Johns Hopkins University (Shinji Watanabe)
2
+ # Northwestern Polytechnical University (Pengcheng Guo)
3
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4
+
5
+ """Encoder definition."""
6
+
7
+ import logging
8
+ import torch
9
+
10
+ from lscodec.models.conformer.convolution import ConvolutionModule
11
+ from lscodec.models.conformer.decoder_layer import DecoderLayer
12
+ from lscodec.models.conformer.nets_utils import get_activation
13
+ from lscodec.models.conformer.vgg2l import VGG2L
14
+ from lscodec.models.conformer.attention import (
15
+ MultiHeadedAttention, # noqa: H301
16
+ RelPositionMultiHeadedAttention, # noqa: H301
17
+ LegacyRelPositionMultiHeadedAttention, # noqa: H301
18
+ )
19
+ from lscodec.models.conformer.embedding import (
20
+ PositionalEncoding, # noqa: H301
21
+ ScaledPositionalEncoding, # noqa: H301
22
+ RelPositionalEncoding, # noqa: H301
23
+ LegacyRelPositionalEncoding, # noqa: H301
24
+ )
25
+ from lscodec.models.conformer.layer_norm import LayerNorm
26
+ from lscodec.models.conformer.multi_layer_conv import Conv1dLinear, MultiLayeredConv1d
27
+ from lscodec.models.conformer.positionwise_feed_forward import (
28
+ PositionwiseFeedForward, # noqa: H301
29
+ )
30
+ from lscodec.models.conformer.repeat import repeat
31
+ from lscodec.models.conformer.subsampling import Conv2dSubsampling
32
+
33
+
34
+ class Decoder(torch.nn.Module):
35
+ """Conformer encoder module.
36
+
37
+ Args:
38
+ idim (int): Input dimension.
39
+ attention_dim (int): Dimention of attention.
40
+ attention_heads (int): The number of heads of multi head attention.
41
+ linear_units (int): The number of units of position-wise feed forward.
42
+ num_blocks (int): The number of decoder blocks.
43
+ dropout_rate (float): Dropout rate.
44
+ positional_dropout_rate (float): Dropout rate after adding positional encoding.
45
+ attention_dropout_rate (float): Dropout rate in attention.
46
+ input_layer (Union[str, torch.nn.Module]): Input layer type.
47
+ normalize_before (bool): Whether to use layer_norm before the first block.
48
+ concat_after (bool): Whether to concat attention layer's input and output.
49
+ if True, additional linear will be applied.
50
+ i.e. x -> x + linear(concat(x, att(x)))
51
+ if False, no additional linear will be applied. i.e. x -> x + att(x)
52
+ positionwise_layer_type (str): "linear", "conv1d", or "conv1d-linear".
53
+ positionwise_conv_kernel_size (int): Kernel size of positionwise conv1d layer.
54
+ macaron_style (bool): Whether to use macaron style for positionwise layer.
55
+ pos_enc_layer_type (str): Encoder positional encoding layer type.
56
+ selfattention_layer_type (str): Encoder attention layer type.
57
+ activation_type (str): Encoder activation function type.
58
+ use_cnn_module (bool): Whether to use convolution module.
59
+ zero_triu (bool): Whether to zero the upper triangular part of attention matrix.
60
+ cnn_module_kernel (int): Kernerl size of convolution module.
61
+ padding_idx (int): Padding idx for input_layer=embed.
62
+
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ idim,
68
+ attention_dim=256,
69
+ attention_heads=4,
70
+ linear_units=2048,
71
+ num_blocks=6,
72
+ dropout_rate=0.1,
73
+ positional_dropout_rate=0.1,
74
+ attention_dropout_rate=0.0,
75
+ input_layer="conv2d",
76
+ normalize_before=True,
77
+ concat_after=False,
78
+ positionwise_layer_type="linear",
79
+ positionwise_conv_kernel_size=1,
80
+ macaron_style=False,
81
+ pos_enc_layer_type="abs_pos",
82
+ selfattention_layer_type="selfattn",
83
+ activation_type="swish",
84
+ use_cnn_module=False,
85
+ zero_triu=False,
86
+ cnn_module_kernel=31,
87
+ padding_idx=-1,
88
+ ):
89
+ """Construct an Encoder object."""
90
+ super(Decoder, self).__init__()
91
+
92
+ activation = get_activation(activation_type)
93
+ if pos_enc_layer_type == "abs_pos":
94
+ pos_enc_class = PositionalEncoding
95
+ elif pos_enc_layer_type == "scaled_abs_pos":
96
+ pos_enc_class = ScaledPositionalEncoding
97
+ elif pos_enc_layer_type == "rel_pos":
98
+ assert selfattention_layer_type == "rel_selfattn"
99
+ pos_enc_class = RelPositionalEncoding
100
+ elif pos_enc_layer_type == "legacy_rel_pos":
101
+ pos_enc_class = LegacyRelPositionalEncoding
102
+ assert selfattention_layer_type == "legacy_rel_selfattn"
103
+ else:
104
+ raise ValueError("unknown pos_enc_layer: " + pos_enc_layer_type)
105
+
106
+ self.conv_subsampling_factor = 1
107
+ if input_layer == "linear":
108
+ self.embed = torch.nn.Sequential(
109
+ torch.nn.Linear(idim, attention_dim),
110
+ torch.nn.LayerNorm(attention_dim),
111
+ torch.nn.Dropout(dropout_rate),
112
+ pos_enc_class(attention_dim, positional_dropout_rate),
113
+ )
114
+ elif input_layer == "conv2d":
115
+ self.embed = Conv2dSubsampling(
116
+ idim,
117
+ attention_dim,
118
+ dropout_rate,
119
+ pos_enc_class(attention_dim, positional_dropout_rate),
120
+ )
121
+ self.conv_subsampling_factor = 4
122
+ elif input_layer == "vgg2l":
123
+ self.embed = VGG2L(idim, attention_dim)
124
+ self.conv_subsampling_factor = 4
125
+ elif input_layer == "embed":
126
+ self.embed = torch.nn.Sequential(
127
+ torch.nn.Embedding(idim, attention_dim, padding_idx=padding_idx),
128
+ pos_enc_class(attention_dim, positional_dropout_rate),
129
+ )
130
+ elif isinstance(input_layer, torch.nn.Module):
131
+ self.embed = torch.nn.Sequential(
132
+ input_layer,
133
+ pos_enc_class(attention_dim, positional_dropout_rate),
134
+ )
135
+ elif input_layer is None:
136
+ self.embed = torch.nn.Sequential(
137
+ pos_enc_class(attention_dim, positional_dropout_rate)
138
+ )
139
+ else:
140
+ raise ValueError("unknown input_layer: " + input_layer)
141
+ self.normalize_before = normalize_before
142
+
143
+ # self-attention module definition
144
+ if selfattention_layer_type == "selfattn":
145
+ logging.info("encoder self-attention layer type = self-attention")
146
+ encoder_selfattn_layer = MultiHeadedAttention
147
+ encoder_selfattn_layer_args = (
148
+ attention_heads,
149
+ attention_dim,
150
+ attention_dropout_rate,
151
+ )
152
+ elif selfattention_layer_type == "legacy_rel_selfattn":
153
+ assert pos_enc_layer_type == "legacy_rel_pos"
154
+ encoder_selfattn_layer = LegacyRelPositionMultiHeadedAttention
155
+ encoder_selfattn_layer_args = (
156
+ attention_heads,
157
+ attention_dim,
158
+ attention_dropout_rate,
159
+ )
160
+ elif selfattention_layer_type == "rel_selfattn":
161
+ logging.info("encoder self-attention layer type = relative self-attention")
162
+ assert pos_enc_layer_type == "rel_pos"
163
+ encoder_selfattn_layer = RelPositionMultiHeadedAttention
164
+ encoder_selfattn_layer_args = (
165
+ attention_heads,
166
+ attention_dim,
167
+ attention_dropout_rate,
168
+ zero_triu,
169
+ )
170
+ else:
171
+ raise ValueError("unknown encoder_attn_layer: " + selfattention_layer_type)
172
+
173
+ # feed-forward module definition
174
+ if positionwise_layer_type == "linear":
175
+ positionwise_layer = PositionwiseFeedForward
176
+ positionwise_layer_args = (
177
+ attention_dim,
178
+ linear_units,
179
+ dropout_rate,
180
+ activation,
181
+ )
182
+ elif positionwise_layer_type == "conv1d":
183
+ positionwise_layer = MultiLayeredConv1d
184
+ positionwise_layer_args = (
185
+ attention_dim,
186
+ linear_units,
187
+ positionwise_conv_kernel_size,
188
+ dropout_rate,
189
+ )
190
+ elif positionwise_layer_type == "conv1d-linear":
191
+ positionwise_layer = Conv1dLinear
192
+ positionwise_layer_args = (
193
+ attention_dim,
194
+ linear_units,
195
+ positionwise_conv_kernel_size,
196
+ dropout_rate,
197
+ )
198
+ else:
199
+ raise NotImplementedError("Support only linear or conv1d.")
200
+
201
+ # convolution module definition
202
+ convolution_layer = ConvolutionModule
203
+ convolution_layer_args = (attention_dim, cnn_module_kernel, activation)
204
+
205
+ self.decoders = repeat(
206
+ num_blocks,
207
+ lambda lnum: DecoderLayer(
208
+ attention_dim,
209
+ encoder_selfattn_layer(*encoder_selfattn_layer_args),
210
+ MultiHeadedAttention(
211
+ attention_heads, attention_dim, attention_dropout_rate
212
+ ),
213
+ positionwise_layer(*positionwise_layer_args),
214
+ positionwise_layer(*positionwise_layer_args) if macaron_style else None,
215
+ convolution_layer(*convolution_layer_args) if use_cnn_module else None,
216
+ dropout_rate,
217
+ normalize_before,
218
+ concat_after,
219
+ ),
220
+ )
221
+ if self.normalize_before:
222
+ self.after_norm = LayerNorm(attention_dim)
223
+
224
+ def forward(self, xs, masks, memory, memory_mask):
225
+ """Encode input sequence.
226
+
227
+ Args:
228
+ xs (torch.Tensor): Input tensor (#batch, time, idim).
229
+ masks (torch.Tensor): Mask tensor (#batch, time).
230
+
231
+ Returns:
232
+ torch.Tensor: Output tensor (#batch, time, attention_dim).
233
+ torch.Tensor: Mask tensor (#batch, time).
234
+
235
+ """
236
+ if isinstance(self.embed, (Conv2dSubsampling, VGG2L)):
237
+ xs, masks = self.embed(xs, masks)
238
+ else:
239
+ xs = self.embed(xs)
240
+
241
+ xs, masks, memory, memory_mask = self.decoders(xs, masks, memory, memory_mask)
242
+ if isinstance(xs, tuple):
243
+ xs = xs[0]
244
+
245
+ if self.normalize_before:
246
+ xs = self.after_norm(xs)
247
+ return xs, masks
lscodec/models/conformer/decoder_layer.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2020 Johns Hopkins University (Shinji Watanabe)
5
+ # Northwestern Polytechnical University (Pengcheng Guo)
6
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
7
+
8
+ """Encoder self-attention layer definition."""
9
+
10
+ import torch
11
+
12
+ from torch import nn
13
+
14
+ from lscodec.models.conformer.layer_norm import LayerNorm
15
+
16
+
17
+ class DecoderLayer(nn.Module):
18
+ """Encoder layer module.
19
+
20
+ Args:
21
+ size (int): Input dimension.
22
+ self_attn (torch.nn.Module): Self-attention module instance.
23
+ `MultiHeadedAttention` or `RelPositionMultiHeadedAttention` instance
24
+ can be used as the argument.
25
+ feed_forward (torch.nn.Module): Feed-forward module instance.
26
+ `PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
27
+ can be used as the argument.
28
+ feed_forward_macaron (torch.nn.Module): Additional feed-forward module instance.
29
+ `PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
30
+ can be used as the argument.
31
+ conv_module (torch.nn.Module): Convolution module instance.
32
+ `ConvlutionModule` instance can be used as the argument.
33
+ dropout_rate (float): Dropout rate.
34
+ normalize_before (bool): Whether to use layer_norm before the first block.
35
+ concat_after (bool): Whether to concat attention layer's input and output.
36
+ if True, additional linear will be applied.
37
+ i.e. x -> x + linear(concat(x, att(x)))
38
+ if False, no additional linear will be applied. i.e. x -> x + att(x)
39
+
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ size,
45
+ self_attn,
46
+ src_attn,
47
+ feed_forward,
48
+ feed_forward_macaron,
49
+ conv_module,
50
+ dropout_rate,
51
+ normalize_before=True,
52
+ concat_after=False,
53
+ ):
54
+ """Construct an EncoderLayer object."""
55
+ super(DecoderLayer, self).__init__()
56
+ self.self_attn = self_attn
57
+ self.src_attn = src_attn
58
+ self.feed_forward = feed_forward
59
+ self.feed_forward_macaron = feed_forward_macaron
60
+ self.conv_module = conv_module
61
+ self.norm_ff = LayerNorm(size) # for the FNN module
62
+ self.norm_mha = LayerNorm(size) # for the MHA module
63
+ self.norm2 = LayerNorm(size) # for the MHA module
64
+ if feed_forward_macaron is not None:
65
+ self.norm_ff_macaron = LayerNorm(size)
66
+ self.ff_scale = 0.5
67
+ else:
68
+ self.ff_scale = 1.0
69
+ if self.conv_module is not None:
70
+ self.norm_conv = LayerNorm(size) # for the CNN module
71
+ self.norm_final = LayerNorm(size) # for the final output of the block
72
+ self.dropout = nn.Dropout(dropout_rate)
73
+ self.size = size
74
+ self.normalize_before = normalize_before
75
+ self.concat_after = concat_after
76
+ if self.concat_after:
77
+ self.concat_linear = nn.Linear(size + size, size)
78
+ self.concat_linear2 = nn.Linear(size + size, size)
79
+
80
+ def forward(self, x_input, mask, memory, memory_mask, cache=None):
81
+ """Compute encoded features.
82
+
83
+ Args:
84
+ x_input (Union[Tuple, torch.Tensor]): Input tensor w/ or w/o pos emb.
85
+ - w/ pos emb: Tuple of tensors [(#batch, time, size), (1, time, size)].
86
+ - w/o pos emb: Tensor (#batch, time, size).
87
+ mask (torch.Tensor): Mask tensor for the input (#batch, time).
88
+ cache (torch.Tensor): Cache tensor of the input (#batch, time - 1, size).
89
+
90
+ Returns:
91
+ torch.Tensor: Output tensor (#batch, time, size).
92
+ torch.Tensor: Mask tensor (#batch, time).
93
+
94
+ """
95
+ if isinstance(x_input, tuple):
96
+ x, pos_emb = x_input[0], x_input[1]
97
+ else:
98
+ x, pos_emb = x_input, None
99
+
100
+ # whether to use macaron style
101
+ if self.feed_forward_macaron is not None:
102
+ residual = x
103
+ if self.normalize_before:
104
+ x = self.norm_ff_macaron(x)
105
+ x = residual + self.ff_scale * self.dropout(self.feed_forward_macaron(x))
106
+ if not self.normalize_before:
107
+ x = self.norm_ff_macaron(x)
108
+
109
+ # multi-headed self-attention module
110
+ residual = x
111
+ if self.normalize_before:
112
+ x = self.norm_mha(x)
113
+
114
+ if cache is None:
115
+ x_q = x
116
+ else:
117
+ assert cache.shape == (x.shape[0], x.shape[1] - 1, self.size)
118
+ x_q = x[:, -1:, :]
119
+ residual = residual[:, -1:, :]
120
+ mask = None if mask is None else mask[:, -1:, :]
121
+
122
+ if pos_emb is not None:
123
+ x_att = self.self_attn(x_q, x, x, pos_emb, mask)
124
+ else:
125
+ x_att = self.self_attn(x_q, x, x, mask)
126
+
127
+ if self.concat_after:
128
+ x_concat = torch.cat((x, x_att), dim=-1)
129
+ x = residual + self.concat_linear(x_concat)
130
+ else:
131
+ x = residual + self.dropout(x_att)
132
+ if not self.normalize_before:
133
+ x = self.norm_mha(x)
134
+
135
+ # cross attention
136
+ residual = x
137
+ if self.normalize_before:
138
+ x = self.norm2(x)
139
+ if self.concat_after:
140
+ x_concat = torch.cat(
141
+ (x, self.src_attn(x, memory, memory, memory_mask)), dim=-1
142
+ )
143
+ x = residual + self.concat_linear2(x_concat)
144
+ else:
145
+ x = residual + self.dropout(self.src_attn(x, memory, memory, memory_mask))
146
+ if not self.normalize_before:
147
+ x = self.norm2(x)
148
+
149
+ # convolution module
150
+ if self.conv_module is not None:
151
+ residual = x
152
+ if self.normalize_before:
153
+ x = self.norm_conv(x)
154
+ x = residual + self.dropout(self.conv_module(x))
155
+ if not self.normalize_before:
156
+ x = self.norm_conv(x)
157
+
158
+ # feed forward module
159
+ residual = x
160
+ if self.normalize_before:
161
+ x = self.norm_ff(x)
162
+ x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
163
+ if not self.normalize_before:
164
+ x = self.norm_ff(x)
165
+
166
+ if self.conv_module is not None:
167
+ x = self.norm_final(x)
168
+
169
+ if cache is not None:
170
+ x = torch.cat([cache, x], dim=1)
171
+
172
+ if pos_emb is not None:
173
+ return (x, pos_emb), mask, memory, memory_mask
174
+
175
+ return x, mask, memory, memory_mask
lscodec/models/conformer/embedding.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Shigeki Karita
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+ """Positional Encoding Module."""
8
+
9
+ import math
10
+
11
+ import torch
12
+
13
+
14
+ def _pre_hook(
15
+ state_dict,
16
+ prefix,
17
+ local_metadata,
18
+ strict,
19
+ missing_keys,
20
+ unexpected_keys,
21
+ error_msgs,
22
+ ):
23
+ """Perform pre-hook in load_state_dict for backward compatibility.
24
+
25
+ Note:
26
+ We saved self.pe until v.0.5.2 but we have omitted it later.
27
+ Therefore, we remove the item "pe" from `state_dict` for backward compatibility.
28
+
29
+ """
30
+ k = prefix + "pe"
31
+ if k in state_dict:
32
+ state_dict.pop(k)
33
+
34
+
35
+ class PositionalEncoding(torch.nn.Module):
36
+ """Positional encoding.
37
+
38
+ Args:
39
+ d_model (int): Embedding dimension.
40
+ dropout_rate (float): Dropout rate.
41
+ max_len (int): Maximum input length.
42
+ reverse (bool): Whether to reverse the input position. Only for
43
+ the class LegacyRelPositionalEncoding. We remove it in the current
44
+ class RelPositionalEncoding.
45
+
46
+ """
47
+
48
+ def __init__(self, d_model, dropout_rate, max_len=5000, reverse=False):
49
+ """Construct an PositionalEncoding object."""
50
+ super(PositionalEncoding, self).__init__()
51
+ self.d_model = d_model
52
+ self.reverse = reverse
53
+ self.xscale = math.sqrt(self.d_model)
54
+ self.dropout = torch.nn.Dropout(p=dropout_rate)
55
+ self.pe = None
56
+ self.extend_pe(torch.tensor(0.0).expand(1, max_len))
57
+ self._register_load_state_dict_pre_hook(_pre_hook)
58
+
59
+ def extend_pe(self, x):
60
+ """Reset the positional encodings."""
61
+ if self.pe is not None:
62
+ if self.pe.size(1) >= x.size(1):
63
+ if self.pe.dtype != x.dtype or self.pe.device != x.device:
64
+ self.pe = self.pe.to(dtype=x.dtype, device=x.device)
65
+ return
66
+ pe = torch.zeros(x.size(1), self.d_model)
67
+ if self.reverse:
68
+ position = torch.arange(
69
+ x.size(1) - 1, -1, -1.0, dtype=torch.float32
70
+ ).unsqueeze(1)
71
+ else:
72
+ position = torch.arange(0, x.size(1), dtype=torch.float32).unsqueeze(1)
73
+ div_term = torch.exp(
74
+ torch.arange(0, self.d_model, 2, dtype=torch.float32)
75
+ * -(math.log(10000.0) / self.d_model)
76
+ )
77
+ pe[:, 0::2] = torch.sin(position * div_term)
78
+ pe[:, 1::2] = torch.cos(position * div_term)
79
+ pe = pe.unsqueeze(0)
80
+ self.pe = pe.to(device=x.device, dtype=x.dtype)
81
+
82
+ def forward(self, x: torch.Tensor):
83
+ """Add positional encoding.
84
+
85
+ Args:
86
+ x (torch.Tensor): Input tensor (batch, time, `*`).
87
+
88
+ Returns:
89
+ torch.Tensor: Encoded tensor (batch, time, `*`).
90
+
91
+ """
92
+ self.extend_pe(x)
93
+ x = x * self.xscale + self.pe[:, : x.size(1)]
94
+ return self.dropout(x)
95
+
96
+
97
+ class ScaledPositionalEncoding(PositionalEncoding):
98
+ """Scaled positional encoding module.
99
+
100
+ See Sec. 3.2 https://arxiv.org/abs/1809.08895
101
+
102
+ Args:
103
+ d_model (int): Embedding dimension.
104
+ dropout_rate (float): Dropout rate.
105
+ max_len (int): Maximum input length.
106
+
107
+ """
108
+
109
+ def __init__(self, d_model, dropout_rate, max_len=5000):
110
+ """Initialize class."""
111
+ super().__init__(d_model=d_model, dropout_rate=dropout_rate, max_len=max_len)
112
+ self.alpha = torch.nn.Parameter(torch.tensor(1.0))
113
+
114
+ def reset_parameters(self):
115
+ """Reset parameters."""
116
+ self.alpha.data = torch.tensor(1.0)
117
+
118
+ def forward(self, x):
119
+ """Add positional encoding.
120
+
121
+ Args:
122
+ x (torch.Tensor): Input tensor (batch, time, `*`).
123
+
124
+ Returns:
125
+ torch.Tensor: Encoded tensor (batch, time, `*`).
126
+
127
+ """
128
+ self.extend_pe(x)
129
+ x = x + self.alpha * self.pe[:, : x.size(1)]
130
+ return self.dropout(x)
131
+
132
+
133
+ class LegacyRelPositionalEncoding(PositionalEncoding):
134
+ """Relative positional encoding module (old version).
135
+
136
+ Details can be found in https://github.com/espnet/espnet/pull/2816.
137
+
138
+ See : Appendix B in https://arxiv.org/abs/1901.02860
139
+
140
+ Args:
141
+ d_model (int): Embedding dimension.
142
+ dropout_rate (float): Dropout rate.
143
+ max_len (int): Maximum input length.
144
+
145
+ """
146
+
147
+ def __init__(self, d_model, dropout_rate, max_len=5000):
148
+ """Initialize class."""
149
+ super().__init__(
150
+ d_model=d_model,
151
+ dropout_rate=dropout_rate,
152
+ max_len=max_len,
153
+ reverse=True,
154
+ )
155
+
156
+ def forward(self, x):
157
+ """Compute positional encoding.
158
+
159
+ Args:
160
+ x (torch.Tensor): Input tensor (batch, time, `*`).
161
+
162
+ Returns:
163
+ torch.Tensor: Encoded tensor (batch, time, `*`).
164
+ torch.Tensor: Positional embedding tensor (1, time, `*`).
165
+
166
+ """
167
+ self.extend_pe(x)
168
+ x = x * self.xscale
169
+ pos_emb = self.pe[:, : x.size(1)]
170
+ return self.dropout(x), self.dropout(pos_emb)
171
+
172
+
173
+ class RelPositionalEncoding(torch.nn.Module):
174
+ """Relative positional encoding module (new implementation).
175
+
176
+ Details can be found in https://github.com/espnet/espnet/pull/2816.
177
+
178
+ See : Appendix B in https://arxiv.org/abs/1901.02860
179
+
180
+ Args:
181
+ d_model (int): Embedding dimension.
182
+ dropout_rate (float): Dropout rate.
183
+ max_len (int): Maximum input length.
184
+
185
+ """
186
+
187
+ def __init__(self, d_model, dropout_rate, max_len=5000):
188
+ """Construct an PositionalEncoding object."""
189
+ super(RelPositionalEncoding, self).__init__()
190
+ self.d_model = d_model
191
+ self.xscale = math.sqrt(self.d_model)
192
+ self.dropout = torch.nn.Dropout(p=dropout_rate)
193
+ self.pe = None
194
+ self.extend_pe(torch.tensor(0.0).expand(1, max_len))
195
+
196
+ def extend_pe(self, x):
197
+ """Reset the positional encodings."""
198
+ if self.pe is not None:
199
+ # self.pe contains both positive and negative parts
200
+ # the length of self.pe is 2 * input_len - 1
201
+ if self.pe.size(1) >= x.size(1) * 2 - 1:
202
+ if self.pe.dtype != x.dtype or self.pe.device != x.device:
203
+ self.pe = self.pe.to(dtype=x.dtype, device=x.device)
204
+ return
205
+ # Suppose `i` means to the position of query vecotr and `j` means the
206
+ # position of key vector. We use position relative positions when keys
207
+ # are to the left (i>j) and negative relative positions otherwise (i<j).
208
+ pe_positive = torch.zeros(x.size(1), self.d_model)
209
+ pe_negative = torch.zeros(x.size(1), self.d_model)
210
+ position = torch.arange(0, x.size(1), dtype=torch.float32).unsqueeze(1)
211
+ div_term = torch.exp(
212
+ torch.arange(0, self.d_model, 2, dtype=torch.float32)
213
+ * -(math.log(10000.0) / self.d_model)
214
+ )
215
+ pe_positive[:, 0::2] = torch.sin(position * div_term)
216
+ pe_positive[:, 1::2] = torch.cos(position * div_term)
217
+ pe_negative[:, 0::2] = torch.sin(-1 * position * div_term)
218
+ pe_negative[:, 1::2] = torch.cos(-1 * position * div_term)
219
+
220
+ # Reserve the order of positive indices and concat both positive and
221
+ # negative indices. This is used to support the shifting trick
222
+ # as in https://arxiv.org/abs/1901.02860
223
+ pe_positive = torch.flip(pe_positive, [0]).unsqueeze(0)
224
+ pe_negative = pe_negative[1:].unsqueeze(0)
225
+ pe = torch.cat([pe_positive, pe_negative], dim=1)
226
+ self.pe = pe.to(device=x.device, dtype=x.dtype)
227
+
228
+ def forward(self, x: torch.Tensor):
229
+ """Add positional encoding.
230
+
231
+ Args:
232
+ x (torch.Tensor): Input tensor (batch, time, `*`).
233
+
234
+ Returns:
235
+ torch.Tensor: Encoded tensor (batch, time, `*`).
236
+
237
+ """
238
+ self.extend_pe(x)
239
+ x = x * self.xscale
240
+ pos_emb = self.pe[
241
+ :,
242
+ self.pe.size(1) // 2 - x.size(1) + 1 : self.pe.size(1) // 2 + x.size(1),
243
+ ]
244
+ return self.dropout(x), self.dropout(pos_emb)
lscodec/models/conformer/encoder.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Johns Hopkins University (Shinji Watanabe)
2
+ # Northwestern Polytechnical University (Pengcheng Guo)
3
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4
+
5
+ """Encoder definition."""
6
+
7
+ import logging
8
+ import torch
9
+ from lscodec.models.conformer.convolution import ConvolutionModule
10
+ from lscodec.models.conformer.encoder_layer import EncoderLayer
11
+ from lscodec.utils.espnet_utils import get_activation
12
+ from lscodec.models.conformer.vgg2l import VGG2L
13
+ from lscodec.models.conformer.attention import (
14
+ MultiHeadedAttention, # noqa: H301
15
+ RelPositionMultiHeadedAttention, # noqa: H301
16
+ LegacyRelPositionMultiHeadedAttention, # noqa: H301
17
+ )
18
+ from lscodec.models.conformer.embedding import (
19
+ PositionalEncoding, # noqa: H301
20
+ ScaledPositionalEncoding, # noqa: H301
21
+ RelPositionalEncoding, # noqa: H301
22
+ LegacyRelPositionalEncoding, # noqa: H301
23
+ )
24
+ from lscodec.models.conformer.layer_norm import LayerNorm
25
+ from lscodec.models.conformer.multi_layer_conv import Conv1dLinear
26
+ from lscodec.models.conformer.multi_layer_conv import MultiLayeredConv1d
27
+ from lscodec.models.conformer.positionwise_feed_forward import (
28
+ PositionwiseFeedForward, # noqa: H301
29
+ )
30
+ from lscodec.models.conformer.repeat import repeat
31
+ from lscodec.models.conformer.subsampling import Conv2dSubsampling
32
+
33
+
34
+
35
+ class Encoder(torch.nn.Module):
36
+ """Conformer encoder module.
37
+
38
+ Args:
39
+ idim (int): Input dimension.
40
+ attention_dim (int): Dimention of attention.
41
+ attention_heads (int): The number of heads of multi head attention.
42
+ linear_units (int): The number of units of position-wise feed forward.
43
+ num_blocks (int): The number of decoder blocks.
44
+ dropout_rate (float): Dropout rate.
45
+ positional_dropout_rate (float): Dropout rate after adding positional encoding.
46
+ attention_dropout_rate (float): Dropout rate in attention.
47
+ input_layer (Union[str, torch.nn.Module]): Input layer type.
48
+ normalize_before (bool): Whether to use layer_norm before the first block.
49
+ concat_after (bool): Whether to concat attention layer's input and output.
50
+ if True, additional linear will be applied.
51
+ i.e. x -> x + linear(concat(x, att(x)))
52
+ if False, no additional linear will be applied. i.e. x -> x + att(x)
53
+ positionwise_layer_type (str): "linear", "conv1d", or "conv1d-linear".
54
+ positionwise_conv_kernel_size (int): Kernel size of positionwise conv1d layer.
55
+ macaron_style (bool): Whether to use macaron style for positionwise layer.
56
+ pos_enc_layer_type (str): Encoder positional encoding layer type.
57
+ selfattention_layer_type (str): Encoder attention layer type.
58
+ activation_type (str): Encoder activation function type.
59
+ use_cnn_module (bool): Whether to use convolution module.
60
+ zero_triu (bool): Whether to zero the upper triangular part of attention matrix.
61
+ cnn_module_kernel (int): Kernerl size of convolution module.
62
+ padding_idx (int): Padding idx for input_layer=embed.
63
+
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ idim,
69
+ attention_dim=256,
70
+ attention_heads=4,
71
+ linear_units=2048,
72
+ num_blocks=6,
73
+ dropout_rate=0.1,
74
+ positional_dropout_rate=0.1,
75
+ attention_dropout_rate=0.0,
76
+ input_layer="conv2d",
77
+ normalize_before=True,
78
+ concat_after=False,
79
+ positionwise_layer_type="linear",
80
+ positionwise_conv_kernel_size=1,
81
+ macaron_style=False,
82
+ pos_enc_layer_type="abs_pos",
83
+ selfattention_layer_type="selfattn",
84
+ activation_type="swish",
85
+ use_cnn_module=False,
86
+ zero_triu=False,
87
+ cnn_module_kernel=31,
88
+ padding_idx=-1,
89
+ ):
90
+ """Construct an Encoder object."""
91
+ super(Encoder, self).__init__()
92
+
93
+ activation = get_activation(activation_type)
94
+ if pos_enc_layer_type == "abs_pos":
95
+ pos_enc_class = PositionalEncoding
96
+ elif pos_enc_layer_type == "scaled_abs_pos":
97
+ pos_enc_class = ScaledPositionalEncoding
98
+ elif pos_enc_layer_type == "rel_pos":
99
+ assert selfattention_layer_type == "rel_selfattn"
100
+ pos_enc_class = RelPositionalEncoding
101
+ elif pos_enc_layer_type == "legacy_rel_pos":
102
+ pos_enc_class = LegacyRelPositionalEncoding
103
+ assert selfattention_layer_type == "legacy_rel_selfattn"
104
+ else:
105
+ raise ValueError("unknown pos_enc_layer: " + pos_enc_layer_type)
106
+
107
+ self.conv_subsampling_factor = 1
108
+ if input_layer == "linear":
109
+ self.embed = torch.nn.Sequential(
110
+ torch.nn.Linear(idim, attention_dim),
111
+ torch.nn.LayerNorm(attention_dim),
112
+ torch.nn.Dropout(dropout_rate),
113
+ pos_enc_class(attention_dim, positional_dropout_rate),
114
+ )
115
+ elif input_layer == "conv2d":
116
+ self.embed = Conv2dSubsampling(
117
+ idim,
118
+ attention_dim,
119
+ dropout_rate,
120
+ pos_enc_class(attention_dim, positional_dropout_rate),
121
+ )
122
+ self.conv_subsampling_factor = 4
123
+ elif input_layer == "vgg2l":
124
+ self.embed = VGG2L(idim, attention_dim)
125
+ self.conv_subsampling_factor = 4
126
+ elif input_layer == "embed":
127
+ self.embed = torch.nn.Sequential(
128
+ torch.nn.Embedding(idim, attention_dim, padding_idx=padding_idx),
129
+ pos_enc_class(attention_dim, positional_dropout_rate),
130
+ )
131
+ elif isinstance(input_layer, torch.nn.Module):
132
+ self.embed = torch.nn.Sequential(
133
+ input_layer,
134
+ pos_enc_class(attention_dim, positional_dropout_rate),
135
+ )
136
+ elif input_layer is None:
137
+ self.embed = torch.nn.Sequential(
138
+ pos_enc_class(attention_dim, positional_dropout_rate)
139
+ )
140
+ elif input_layer == "no_pos_emb":
141
+ self.embed = torch.nn.Identity()
142
+ else:
143
+ raise ValueError("unknown input_layer: " + input_layer)
144
+ self.normalize_before = normalize_before
145
+
146
+ # self-attention module definition
147
+ if selfattention_layer_type == "selfattn":
148
+ logging.info("encoder self-attention layer type = self-attention")
149
+ encoder_selfattn_layer = MultiHeadedAttention
150
+ encoder_selfattn_layer_args = (
151
+ attention_heads,
152
+ attention_dim,
153
+ attention_dropout_rate,
154
+ )
155
+ elif selfattention_layer_type == "legacy_rel_selfattn":
156
+ assert pos_enc_layer_type == "legacy_rel_pos"
157
+ encoder_selfattn_layer = LegacyRelPositionMultiHeadedAttention
158
+ encoder_selfattn_layer_args = (
159
+ attention_heads,
160
+ attention_dim,
161
+ attention_dropout_rate,
162
+ )
163
+ elif selfattention_layer_type == "rel_selfattn":
164
+ logging.info("encoder self-attention layer type = relative self-attention")
165
+ assert pos_enc_layer_type == "rel_pos"
166
+ encoder_selfattn_layer = RelPositionMultiHeadedAttention
167
+ encoder_selfattn_layer_args = (
168
+ attention_heads,
169
+ attention_dim,
170
+ attention_dropout_rate,
171
+ zero_triu,
172
+ )
173
+ else:
174
+ raise ValueError("unknown encoder_attn_layer: " + selfattention_layer_type)
175
+
176
+ # feed-forward module definition
177
+ if positionwise_layer_type == "linear":
178
+ positionwise_layer = PositionwiseFeedForward
179
+ positionwise_layer_args = (
180
+ attention_dim,
181
+ linear_units,
182
+ dropout_rate,
183
+ activation,
184
+ )
185
+ elif positionwise_layer_type == "conv1d":
186
+ positionwise_layer = MultiLayeredConv1d
187
+ positionwise_layer_args = (
188
+ attention_dim,
189
+ linear_units,
190
+ positionwise_conv_kernel_size,
191
+ dropout_rate,
192
+ )
193
+ elif positionwise_layer_type == "conv1d-linear":
194
+ positionwise_layer = Conv1dLinear
195
+ positionwise_layer_args = (
196
+ attention_dim,
197
+ linear_units,
198
+ positionwise_conv_kernel_size,
199
+ dropout_rate,
200
+ )
201
+ else:
202
+ raise NotImplementedError("Support only linear or conv1d.")
203
+
204
+ # convolution module definition
205
+ convolution_layer = ConvolutionModule
206
+ convolution_layer_args = (attention_dim, cnn_module_kernel, activation)
207
+
208
+ self.encoders = repeat(
209
+ num_blocks,
210
+ lambda lnum: EncoderLayer(
211
+ attention_dim,
212
+ encoder_selfattn_layer(*encoder_selfattn_layer_args),
213
+ positionwise_layer(*positionwise_layer_args),
214
+ positionwise_layer(*positionwise_layer_args) if macaron_style else None,
215
+ convolution_layer(*convolution_layer_args) if use_cnn_module else None,
216
+ dropout_rate,
217
+ normalize_before,
218
+ concat_after,
219
+ ),
220
+ )
221
+ if self.normalize_before:
222
+ self.after_norm = LayerNorm(attention_dim)
223
+
224
+ def forward(self, xs, masks):
225
+ """Encode input sequence.
226
+
227
+ Args:
228
+ xs (torch.Tensor): Input tensor (#batch, time, idim).
229
+ masks (torch.Tensor): Mask tensor (#batch, time).
230
+
231
+ Returns:
232
+ torch.Tensor: Output tensor (#batch, time, attention_dim).
233
+ torch.Tensor: Mask tensor (#batch, time).
234
+
235
+ """
236
+ if isinstance(self.embed, (Conv2dSubsampling, VGG2L)):
237
+ xs, masks = self.embed(xs, masks)
238
+ else:
239
+ xs = self.embed(xs)
240
+
241
+ xs, masks = self.encoders(xs, masks)
242
+ if isinstance(xs, tuple):
243
+ xs = xs[0]
244
+
245
+ if self.normalize_before:
246
+ xs = self.after_norm(xs)
247
+ return xs, masks
lscodec/models/conformer/encoder_layer.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2020 Johns Hopkins University (Shinji Watanabe)
5
+ # Northwestern Polytechnical University (Pengcheng Guo)
6
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
7
+
8
+ """Encoder self-attention layer definition."""
9
+
10
+ import torch
11
+
12
+ from torch import nn
13
+
14
+ from lscodec.models.conformer.layer_norm import LayerNorm
15
+
16
+
17
+ class EncoderLayer(nn.Module):
18
+ """Encoder layer module.
19
+
20
+ Args:
21
+ size (int): Input dimension.
22
+ self_attn (torch.nn.Module): Self-attention module instance.
23
+ `MultiHeadedAttention` or `RelPositionMultiHeadedAttention` instance
24
+ can be used as the argument.
25
+ feed_forward (torch.nn.Module): Feed-forward module instance.
26
+ `PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
27
+ can be used as the argument.
28
+ feed_forward_macaron (torch.nn.Module): Additional feed-forward module instance.
29
+ `PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
30
+ can be used as the argument.
31
+ conv_module (torch.nn.Module): Convolution module instance.
32
+ `ConvlutionModule` instance can be used as the argument.
33
+ dropout_rate (float): Dropout rate.
34
+ normalize_before (bool): Whether to use layer_norm before the first block.
35
+ concat_after (bool): Whether to concat attention layer's input and output.
36
+ if True, additional linear will be applied.
37
+ i.e. x -> x + linear(concat(x, att(x)))
38
+ if False, no additional linear will be applied. i.e. x -> x + att(x)
39
+
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ size,
45
+ self_attn,
46
+ feed_forward,
47
+ feed_forward_macaron,
48
+ conv_module,
49
+ dropout_rate,
50
+ normalize_before=True,
51
+ concat_after=False,
52
+ ):
53
+ """Construct an EncoderLayer object."""
54
+ super(EncoderLayer, self).__init__()
55
+ self.self_attn = self_attn
56
+ self.feed_forward = feed_forward
57
+ self.feed_forward_macaron = feed_forward_macaron
58
+ self.conv_module = conv_module
59
+ self.norm_ff = LayerNorm(size) # for the FNN module
60
+ self.norm_mha = LayerNorm(size) # for the MHA module
61
+ if feed_forward_macaron is not None:
62
+ self.norm_ff_macaron = LayerNorm(size)
63
+ self.ff_scale = 0.5
64
+ else:
65
+ self.ff_scale = 1.0
66
+ if self.conv_module is not None:
67
+ self.norm_conv = LayerNorm(size) # for the CNN module
68
+ self.norm_final = LayerNorm(size) # for the final output of the block
69
+ self.dropout = nn.Dropout(dropout_rate)
70
+ self.size = size
71
+ self.normalize_before = normalize_before
72
+ self.concat_after = concat_after
73
+ if self.concat_after:
74
+ self.concat_linear = nn.Linear(size + size, size)
75
+
76
+ def forward(self, x_input, mask, cache=None):
77
+ """Compute encoded features.
78
+
79
+ Args:
80
+ x_input (Union[Tuple, torch.Tensor]): Input tensor w/ or w/o pos emb.
81
+ - w/ pos emb: Tuple of tensors [(#batch, time, size), (1, time, size)].
82
+ - w/o pos emb: Tensor (#batch, time, size).
83
+ mask (torch.Tensor): Mask tensor for the input (#batch, time).
84
+ cache (torch.Tensor): Cache tensor of the input (#batch, time - 1, size).
85
+
86
+ Returns:
87
+ torch.Tensor: Output tensor (#batch, time, size).
88
+ torch.Tensor: Mask tensor (#batch, time).
89
+
90
+ """
91
+ if isinstance(x_input, tuple):
92
+ x, pos_emb = x_input[0], x_input[1]
93
+ else:
94
+ x, pos_emb = x_input, None
95
+
96
+ # whether to use macaron style
97
+ if self.feed_forward_macaron is not None:
98
+ residual = x
99
+ if self.normalize_before:
100
+ x = self.norm_ff_macaron(x)
101
+ x = residual + self.ff_scale * self.dropout(self.feed_forward_macaron(x))
102
+ if not self.normalize_before:
103
+ x = self.norm_ff_macaron(x)
104
+
105
+ # multi-headed self-attention module
106
+ residual = x
107
+ if self.normalize_before:
108
+ x = self.norm_mha(x)
109
+
110
+ if cache is None:
111
+ x_q = x
112
+ else:
113
+ assert cache.shape == (x.shape[0], x.shape[1] - 1, self.size)
114
+ x_q = x[:, -1:, :]
115
+ residual = residual[:, -1:, :]
116
+ mask = None if mask is None else mask[:, -1:, :]
117
+
118
+ if pos_emb is not None:
119
+ x_att = self.self_attn(x_q, x, x, pos_emb, mask)
120
+ else:
121
+ x_att = self.self_attn(x_q, x, x, mask)
122
+
123
+ if self.concat_after:
124
+ x_concat = torch.cat((x, x_att), dim=-1)
125
+ x = residual + self.concat_linear(x_concat)
126
+ else:
127
+ x = residual + self.dropout(x_att)
128
+ if not self.normalize_before:
129
+ x = self.norm_mha(x)
130
+
131
+ # convolution module
132
+ if self.conv_module is not None:
133
+ residual = x
134
+ if self.normalize_before:
135
+ x = self.norm_conv(x)
136
+ x = residual + self.dropout(self.conv_module(x))
137
+ if not self.normalize_before:
138
+ x = self.norm_conv(x)
139
+
140
+ # feed forward module
141
+ residual = x
142
+ if self.normalize_before:
143
+ x = self.norm_ff(x)
144
+ x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
145
+ if not self.normalize_before:
146
+ x = self.norm_ff(x)
147
+
148
+ if self.conv_module is not None:
149
+ x = self.norm_final(x)
150
+
151
+ if cache is not None:
152
+ x = torch.cat([cache, x], dim=1)
153
+
154
+ if pos_emb is not None:
155
+ return (x, pos_emb), mask
156
+
157
+ return x, mask
lscodec/models/conformer/layer_norm.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Shigeki Karita
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+ """Layer normalization module."""
8
+
9
+ import torch
10
+
11
+
12
+ class LayerNorm(torch.nn.LayerNorm):
13
+ """Layer normalization module.
14
+
15
+ Args:
16
+ nout (int): Output dim size.
17
+ dim (int): Dimension to be normalized.
18
+
19
+ """
20
+
21
+ def __init__(self, nout, dim=-1):
22
+ """Construct an LayerNorm object."""
23
+ super(LayerNorm, self).__init__(nout, eps=1e-12)
24
+ self.dim = dim
25
+
26
+ def forward(self, x):
27
+ """Apply layer normalization.
28
+
29
+ Args:
30
+ x (torch.Tensor): Input tensor.
31
+
32
+ Returns:
33
+ torch.Tensor: Normalized tensor.
34
+
35
+ """
36
+ if self.dim == -1:
37
+ return super(LayerNorm, self).forward(x)
38
+ return (
39
+ super(LayerNorm, self)
40
+ .forward(x.transpose(self.dim, -1))
41
+ .transpose(self.dim, -1)
42
+ )
lscodec/models/conformer/multi_layer_conv.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Tomoki Hayashi
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+ """Layer modules for FFT block in FastSpeech (Feed-forward Transformer)."""
8
+
9
+ import torch
10
+
11
+
12
+ class MultiLayeredConv1d(torch.nn.Module):
13
+ """Multi-layered conv1d for Transformer block.
14
+
15
+ This is a module of multi-leyered conv1d designed
16
+ to replace positionwise feed-forward network
17
+ in Transforner block, which is introduced in
18
+ `FastSpeech: Fast, Robust and Controllable Text to Speech`_.
19
+
20
+ .. _`FastSpeech: Fast, Robust and Controllable Text to Speech`:
21
+ https://arxiv.org/pdf/1905.09263.pdf
22
+
23
+ """
24
+
25
+ def __init__(self, in_chans, hidden_chans, kernel_size, dropout_rate):
26
+ """Initialize MultiLayeredConv1d module.
27
+
28
+ Args:
29
+ in_chans (int): Number of input channels.
30
+ hidden_chans (int): Number of hidden channels.
31
+ kernel_size (int): Kernel size of conv1d.
32
+ dropout_rate (float): Dropout rate.
33
+
34
+ """
35
+ super(MultiLayeredConv1d, self).__init__()
36
+ self.w_1 = torch.nn.Conv1d(
37
+ in_chans,
38
+ hidden_chans,
39
+ kernel_size,
40
+ stride=1,
41
+ padding=(kernel_size - 1) // 2,
42
+ )
43
+ self.w_2 = torch.nn.Conv1d(
44
+ hidden_chans,
45
+ in_chans,
46
+ kernel_size,
47
+ stride=1,
48
+ padding=(kernel_size - 1) // 2,
49
+ )
50
+ self.dropout = torch.nn.Dropout(dropout_rate)
51
+
52
+ def forward(self, x):
53
+ """Calculate forward propagation.
54
+
55
+ Args:
56
+ x (torch.Tensor): Batch of input tensors (B, T, in_chans).
57
+
58
+ Returns:
59
+ torch.Tensor: Batch of output tensors (B, T, hidden_chans).
60
+
61
+ """
62
+ x = torch.relu(self.w_1(x.transpose(-1, 1))).transpose(-1, 1)
63
+ return self.w_2(self.dropout(x).transpose(-1, 1)).transpose(-1, 1)
64
+
65
+
66
+ class Conv1dLinear(torch.nn.Module):
67
+ """Conv1D + Linear for Transformer block.
68
+
69
+ A variant of MultiLayeredConv1d, which replaces second conv-layer to linear.
70
+
71
+ """
72
+
73
+ def __init__(self, in_chans, hidden_chans, kernel_size, dropout_rate):
74
+ """Initialize Conv1dLinear module.
75
+
76
+ Args:
77
+ in_chans (int): Number of input channels.
78
+ hidden_chans (int): Number of hidden channels.
79
+ kernel_size (int): Kernel size of conv1d.
80
+ dropout_rate (float): Dropout rate.
81
+
82
+ """
83
+ super(Conv1dLinear, self).__init__()
84
+ self.w_1 = torch.nn.Conv1d(
85
+ in_chans,
86
+ hidden_chans,
87
+ kernel_size,
88
+ stride=1,
89
+ padding=(kernel_size - 1) // 2,
90
+ )
91
+ self.w_2 = torch.nn.Linear(hidden_chans, in_chans)
92
+ self.dropout = torch.nn.Dropout(dropout_rate)
93
+
94
+ def forward(self, x):
95
+ """Calculate forward propagation.
96
+
97
+ Args:
98
+ x (torch.Tensor): Batch of input tensors (B, T, in_chans).
99
+
100
+ Returns:
101
+ torch.Tensor: Batch of output tensors (B, T, hidden_chans).
102
+
103
+ """
104
+ x = torch.relu(self.w_1(x.transpose(-1, 1))).transpose(-1, 1)
105
+ return self.w_2(self.dropout(x))
lscodec/models/conformer/nets_utils.py ADDED
@@ -0,0 +1,498 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Network related utility tools."""
4
+
5
+ import logging
6
+ from typing import Dict
7
+
8
+ import numpy as np
9
+ import torch
10
+
11
+
12
+ def to_device(m, x):
13
+ """Send tensor into the device of the module.
14
+
15
+ Args:
16
+ m (torch.nn.Module): Torch module.
17
+ x (Tensor): Torch tensor.
18
+
19
+ Returns:
20
+ Tensor: Torch tensor located in the same place as torch module.
21
+
22
+ """
23
+ if isinstance(m, torch.nn.Module):
24
+ device = next(m.parameters()).device
25
+ elif isinstance(m, torch.Tensor):
26
+ device = m.device
27
+ else:
28
+ raise TypeError(
29
+ "Expected torch.nn.Module or torch.tensor, " f"bot got: {type(m)}"
30
+ )
31
+ return x.to(device)
32
+
33
+
34
+ def pad_list(xs, pad_value):
35
+ """Perform padding for the list of tensors.
36
+
37
+ Args:
38
+ xs (List): List of Tensors [(T_1, `*`), (T_2, `*`), ..., (T_B, `*`)].
39
+ pad_value (float): Value for padding.
40
+
41
+ Returns:
42
+ Tensor: Padded tensor (B, Tmax, `*`).
43
+
44
+ Examples:
45
+ >>> x = [torch.ones(4), torch.ones(2), torch.ones(1)]
46
+ >>> x
47
+ [tensor([1., 1., 1., 1.]), tensor([1., 1.]), tensor([1.])]
48
+ >>> pad_list(x, 0)
49
+ tensor([[1., 1., 1., 1.],
50
+ [1., 1., 0., 0.],
51
+ [1., 0., 0., 0.]])
52
+
53
+ """
54
+ n_batch = len(xs)
55
+ max_len = max(x.size(0) for x in xs)
56
+ pad = xs[0].new(n_batch, max_len, *xs[0].size()[1:]).fill_(pad_value)
57
+
58
+ for i in range(n_batch):
59
+ pad[i, : xs[i].size(0)] = xs[i]
60
+
61
+ return pad
62
+
63
+
64
+ def make_pad_mask(lengths, xs=None, length_dim=-1):
65
+ """Make mask tensor containing indices of padded part.
66
+
67
+ Args:
68
+ lengths (LongTensor or List): Batch of lengths (B,).
69
+ xs (Tensor, optional): The reference tensor.
70
+ If set, masks will be the same shape as this tensor.
71
+ length_dim (int, optional): Dimension indicator of the above tensor.
72
+ See the example.
73
+
74
+ Returns:
75
+ Tensor: Mask tensor containing indices of padded part.
76
+ dtype=torch.uint8 in PyTorch 1.2-
77
+ dtype=torch.bool in PyTorch 1.2+ (including 1.2)
78
+
79
+ Examples:
80
+ With only lengths.
81
+
82
+ >>> lengths = [5, 3, 2]
83
+ >>> make_non_pad_mask(lengths)
84
+ masks = [[0, 0, 0, 0 ,0],
85
+ [0, 0, 0, 1, 1],
86
+ [0, 0, 1, 1, 1]]
87
+
88
+ With the reference tensor.
89
+
90
+ >>> xs = torch.zeros((3, 2, 4))
91
+ >>> make_pad_mask(lengths, xs)
92
+ tensor([[[0, 0, 0, 0],
93
+ [0, 0, 0, 0]],
94
+ [[0, 0, 0, 1],
95
+ [0, 0, 0, 1]],
96
+ [[0, 0, 1, 1],
97
+ [0, 0, 1, 1]]], dtype=torch.uint8)
98
+ >>> xs = torch.zeros((3, 2, 6))
99
+ >>> make_pad_mask(lengths, xs)
100
+ tensor([[[0, 0, 0, 0, 0, 1],
101
+ [0, 0, 0, 0, 0, 1]],
102
+ [[0, 0, 0, 1, 1, 1],
103
+ [0, 0, 0, 1, 1, 1]],
104
+ [[0, 0, 1, 1, 1, 1],
105
+ [0, 0, 1, 1, 1, 1]]], dtype=torch.uint8)
106
+
107
+ With the reference tensor and dimension indicator.
108
+
109
+ >>> xs = torch.zeros((3, 6, 6))
110
+ >>> make_pad_mask(lengths, xs, 1)
111
+ tensor([[[0, 0, 0, 0, 0, 0],
112
+ [0, 0, 0, 0, 0, 0],
113
+ [0, 0, 0, 0, 0, 0],
114
+ [0, 0, 0, 0, 0, 0],
115
+ [0, 0, 0, 0, 0, 0],
116
+ [1, 1, 1, 1, 1, 1]],
117
+ [[0, 0, 0, 0, 0, 0],
118
+ [0, 0, 0, 0, 0, 0],
119
+ [0, 0, 0, 0, 0, 0],
120
+ [1, 1, 1, 1, 1, 1],
121
+ [1, 1, 1, 1, 1, 1],
122
+ [1, 1, 1, 1, 1, 1]],
123
+ [[0, 0, 0, 0, 0, 0],
124
+ [0, 0, 0, 0, 0, 0],
125
+ [1, 1, 1, 1, 1, 1],
126
+ [1, 1, 1, 1, 1, 1],
127
+ [1, 1, 1, 1, 1, 1],
128
+ [1, 1, 1, 1, 1, 1]]], dtype=torch.uint8)
129
+ >>> make_pad_mask(lengths, xs, 2)
130
+ tensor([[[0, 0, 0, 0, 0, 1],
131
+ [0, 0, 0, 0, 0, 1],
132
+ [0, 0, 0, 0, 0, 1],
133
+ [0, 0, 0, 0, 0, 1],
134
+ [0, 0, 0, 0, 0, 1],
135
+ [0, 0, 0, 0, 0, 1]],
136
+ [[0, 0, 0, 1, 1, 1],
137
+ [0, 0, 0, 1, 1, 1],
138
+ [0, 0, 0, 1, 1, 1],
139
+ [0, 0, 0, 1, 1, 1],
140
+ [0, 0, 0, 1, 1, 1],
141
+ [0, 0, 0, 1, 1, 1]],
142
+ [[0, 0, 1, 1, 1, 1],
143
+ [0, 0, 1, 1, 1, 1],
144
+ [0, 0, 1, 1, 1, 1],
145
+ [0, 0, 1, 1, 1, 1],
146
+ [0, 0, 1, 1, 1, 1],
147
+ [0, 0, 1, 1, 1, 1]]], dtype=torch.uint8)
148
+
149
+ """
150
+ if length_dim == 0:
151
+ raise ValueError("length_dim cannot be 0: {}".format(length_dim))
152
+
153
+ if not isinstance(lengths, list):
154
+ lengths = lengths.tolist()
155
+ bs = int(len(lengths))
156
+ if xs is None:
157
+ maxlen = int(max(lengths))
158
+ else:
159
+ maxlen = xs.size(length_dim)
160
+
161
+ seq_range = torch.arange(0, maxlen, dtype=torch.int64)
162
+ seq_range_expand = seq_range.unsqueeze(0).expand(bs, maxlen)
163
+ seq_length_expand = seq_range_expand.new(lengths).unsqueeze(-1)
164
+ mask = seq_range_expand >= seq_length_expand
165
+
166
+ if xs is not None:
167
+ assert xs.size(0) == bs, (xs.size(0), bs)
168
+
169
+ if length_dim < 0:
170
+ length_dim = xs.dim() + length_dim
171
+ # ind = (:, None, ..., None, :, , None, ..., None)
172
+ ind = tuple(
173
+ slice(None) if i in (0, length_dim) else None for i in range(xs.dim())
174
+ )
175
+ mask = mask[ind].expand_as(xs).to(xs.device)
176
+ return mask
177
+
178
+
179
+ def make_non_pad_mask(lengths, xs=None, length_dim=-1):
180
+ """Make mask tensor containing indices of non-padded part.
181
+
182
+ Args:
183
+ lengths (LongTensor or List): Batch of lengths (B,).
184
+ xs (Tensor, optional): The reference tensor.
185
+ If set, masks will be the same shape as this tensor.
186
+ length_dim (int, optional): Dimension indicator of the above tensor.
187
+ See the example.
188
+
189
+ Returns:
190
+ ByteTensor: mask tensor containing indices of padded part.
191
+ dtype=torch.uint8 in PyTorch 1.2-
192
+ dtype=torch.bool in PyTorch 1.2+ (including 1.2)
193
+
194
+ Examples:
195
+ With only lengths.
196
+
197
+ >>> lengths = [5, 3, 2]
198
+ >>> make_non_pad_mask(lengths)
199
+ masks = [[1, 1, 1, 1 ,1],
200
+ [1, 1, 1, 0, 0],
201
+ [1, 1, 0, 0, 0]]
202
+
203
+ With the reference tensor.
204
+
205
+ >>> xs = torch.zeros((3, 2, 4))
206
+ >>> make_non_pad_mask(lengths, xs)
207
+ tensor([[[1, 1, 1, 1],
208
+ [1, 1, 1, 1]],
209
+ [[1, 1, 1, 0],
210
+ [1, 1, 1, 0]],
211
+ [[1, 1, 0, 0],
212
+ [1, 1, 0, 0]]], dtype=torch.uint8)
213
+ >>> xs = torch.zeros((3, 2, 6))
214
+ >>> make_non_pad_mask(lengths, xs)
215
+ tensor([[[1, 1, 1, 1, 1, 0],
216
+ [1, 1, 1, 1, 1, 0]],
217
+ [[1, 1, 1, 0, 0, 0],
218
+ [1, 1, 1, 0, 0, 0]],
219
+ [[1, 1, 0, 0, 0, 0],
220
+ [1, 1, 0, 0, 0, 0]]], dtype=torch.uint8)
221
+
222
+ With the reference tensor and dimension indicator.
223
+
224
+ >>> xs = torch.zeros((3, 6, 6))
225
+ >>> make_non_pad_mask(lengths, xs, 1)
226
+ tensor([[[1, 1, 1, 1, 1, 1],
227
+ [1, 1, 1, 1, 1, 1],
228
+ [1, 1, 1, 1, 1, 1],
229
+ [1, 1, 1, 1, 1, 1],
230
+ [1, 1, 1, 1, 1, 1],
231
+ [0, 0, 0, 0, 0, 0]],
232
+ [[1, 1, 1, 1, 1, 1],
233
+ [1, 1, 1, 1, 1, 1],
234
+ [1, 1, 1, 1, 1, 1],
235
+ [0, 0, 0, 0, 0, 0],
236
+ [0, 0, 0, 0, 0, 0],
237
+ [0, 0, 0, 0, 0, 0]],
238
+ [[1, 1, 1, 1, 1, 1],
239
+ [1, 1, 1, 1, 1, 1],
240
+ [0, 0, 0, 0, 0, 0],
241
+ [0, 0, 0, 0, 0, 0],
242
+ [0, 0, 0, 0, 0, 0],
243
+ [0, 0, 0, 0, 0, 0]]], dtype=torch.uint8)
244
+ >>> make_non_pad_mask(lengths, xs, 2)
245
+ tensor([[[1, 1, 1, 1, 1, 0],
246
+ [1, 1, 1, 1, 1, 0],
247
+ [1, 1, 1, 1, 1, 0],
248
+ [1, 1, 1, 1, 1, 0],
249
+ [1, 1, 1, 1, 1, 0],
250
+ [1, 1, 1, 1, 1, 0]],
251
+ [[1, 1, 1, 0, 0, 0],
252
+ [1, 1, 1, 0, 0, 0],
253
+ [1, 1, 1, 0, 0, 0],
254
+ [1, 1, 1, 0, 0, 0],
255
+ [1, 1, 1, 0, 0, 0],
256
+ [1, 1, 1, 0, 0, 0]],
257
+ [[1, 1, 0, 0, 0, 0],
258
+ [1, 1, 0, 0, 0, 0],
259
+ [1, 1, 0, 0, 0, 0],
260
+ [1, 1, 0, 0, 0, 0],
261
+ [1, 1, 0, 0, 0, 0],
262
+ [1, 1, 0, 0, 0, 0]]], dtype=torch.uint8)
263
+
264
+ """
265
+ return ~make_pad_mask(lengths, xs, length_dim)
266
+
267
+
268
+ def mask_by_length(xs, lengths, fill=0):
269
+ """Mask tensor according to length.
270
+
271
+ Args:
272
+ xs (Tensor): Batch of input tensor (B, `*`).
273
+ lengths (LongTensor or List): Batch of lengths (B,).
274
+ fill (int or float): Value to fill masked part.
275
+
276
+ Returns:
277
+ Tensor: Batch of masked input tensor (B, `*`).
278
+
279
+ Examples:
280
+ >>> x = torch.arange(5).repeat(3, 1) + 1
281
+ >>> x
282
+ tensor([[1, 2, 3, 4, 5],
283
+ [1, 2, 3, 4, 5],
284
+ [1, 2, 3, 4, 5]])
285
+ >>> lengths = [5, 3, 2]
286
+ >>> mask_by_length(x, lengths)
287
+ tensor([[1, 2, 3, 4, 5],
288
+ [1, 2, 3, 0, 0],
289
+ [1, 2, 0, 0, 0]])
290
+
291
+ """
292
+ assert xs.size(0) == len(lengths)
293
+ ret = xs.data.new(*xs.size()).fill_(fill)
294
+ for i, l in enumerate(lengths):
295
+ ret[i, :l] = xs[i, :l]
296
+ return ret
297
+
298
+
299
+ def th_accuracy(pad_outputs, pad_targets, ignore_label):
300
+ """Calculate accuracy.
301
+
302
+ Args:
303
+ pad_outputs (Tensor): Prediction tensors (B * Lmax, D).
304
+ pad_targets (LongTensor): Target label tensors (B, Lmax, D).
305
+ ignore_label (int): Ignore label id.
306
+
307
+ Returns:
308
+ float: Accuracy value (0.0 - 1.0).
309
+
310
+ """
311
+ pad_pred = pad_outputs.view(
312
+ pad_targets.size(0), pad_targets.size(1), pad_outputs.size(1)
313
+ ).argmax(2)
314
+ mask = pad_targets != ignore_label
315
+ numerator = torch.sum(
316
+ pad_pred.masked_select(mask) == pad_targets.masked_select(mask)
317
+ )
318
+ denominator = torch.sum(mask)
319
+ return float(numerator) / float(denominator)
320
+
321
+
322
+ def to_torch_tensor(x):
323
+ """Change to torch.Tensor or ComplexTensor from numpy.ndarray.
324
+
325
+ Args:
326
+ x: Inputs. It should be one of numpy.ndarray, Tensor, ComplexTensor, and dict.
327
+
328
+ Returns:
329
+ Tensor or ComplexTensor: Type converted inputs.
330
+
331
+ Examples:
332
+ >>> xs = np.ones(3, dtype=np.float32)
333
+ >>> xs = to_torch_tensor(xs)
334
+ tensor([1., 1., 1.])
335
+ >>> xs = torch.ones(3, 4, 5)
336
+ >>> assert to_torch_tensor(xs) is xs
337
+ >>> xs = {'real': xs, 'imag': xs}
338
+ >>> to_torch_tensor(xs)
339
+ ComplexTensor(
340
+ Real:
341
+ tensor([1., 1., 1.])
342
+ Imag;
343
+ tensor([1., 1., 1.])
344
+ )
345
+
346
+ """
347
+ # If numpy, change to torch tensor
348
+ if isinstance(x, np.ndarray):
349
+ if x.dtype.kind == "c":
350
+ # Dynamically importing because torch_complex requires python3
351
+ from torch_complex.tensor import ComplexTensor
352
+
353
+ return ComplexTensor(x)
354
+ else:
355
+ return torch.from_numpy(x)
356
+
357
+ # If {'real': ..., 'imag': ...}, convert to ComplexTensor
358
+ elif isinstance(x, dict):
359
+ # Dynamically importing because torch_complex requires python3
360
+ from torch_complex.tensor import ComplexTensor
361
+
362
+ if "real" not in x or "imag" not in x:
363
+ raise ValueError("has 'real' and 'imag' keys: {}".format(list(x)))
364
+ # Relative importing because of using python3 syntax
365
+ return ComplexTensor(x["real"], x["imag"])
366
+
367
+ # If torch.Tensor, as it is
368
+ elif isinstance(x, torch.Tensor):
369
+ return x
370
+
371
+ else:
372
+ error = (
373
+ "x must be numpy.ndarray, torch.Tensor or a dict like "
374
+ "{{'real': torch.Tensor, 'imag': torch.Tensor}}, "
375
+ "but got {}".format(type(x))
376
+ )
377
+ try:
378
+ from torch_complex.tensor import ComplexTensor
379
+ except Exception:
380
+ # If PY2
381
+ raise ValueError(error)
382
+ else:
383
+ # If PY3
384
+ if isinstance(x, ComplexTensor):
385
+ return x
386
+ else:
387
+ raise ValueError(error)
388
+
389
+
390
+ def get_subsample(train_args, mode, arch):
391
+ """Parse the subsampling factors from the args for the specified `mode` and `arch`.
392
+
393
+ Args:
394
+ train_args: argument Namespace containing options.
395
+ mode: one of ('asr', 'mt', 'st')
396
+ arch: one of ('rnn', 'rnn-t', 'rnn_mix', 'rnn_mulenc', 'transformer')
397
+
398
+ Returns:
399
+ np.ndarray / List[np.ndarray]: subsampling factors.
400
+ """
401
+ if arch == "transformer":
402
+ return np.array([1])
403
+
404
+ elif mode == "mt" and arch == "rnn":
405
+ # +1 means input (+1) and layers outputs (train_args.elayer)
406
+ subsample = np.ones(train_args.elayers + 1, dtype=np.int)
407
+ logging.warning("Subsampling is not performed for machine translation.")
408
+ logging.info("subsample: " + " ".join([str(x) for x in subsample]))
409
+ return subsample
410
+
411
+ elif (
412
+ (mode == "asr" and arch in ("rnn", "rnn-t"))
413
+ or (mode == "mt" and arch == "rnn")
414
+ or (mode == "st" and arch == "rnn")
415
+ ):
416
+ subsample = np.ones(train_args.elayers + 1, dtype=np.int)
417
+ if train_args.etype.endswith("p") and not train_args.etype.startswith("vgg"):
418
+ ss = train_args.subsample.split("_")
419
+ for j in range(min(train_args.elayers + 1, len(ss))):
420
+ subsample[j] = int(ss[j])
421
+ else:
422
+ logging.warning(
423
+ "Subsampling is not performed for vgg*. "
424
+ "It is performed in max pooling layers at CNN."
425
+ )
426
+ logging.info("subsample: " + " ".join([str(x) for x in subsample]))
427
+ return subsample
428
+
429
+ elif mode == "asr" and arch == "rnn_mix":
430
+ subsample = np.ones(
431
+ train_args.elayers_sd + train_args.elayers + 1, dtype=np.int
432
+ )
433
+ if train_args.etype.endswith("p") and not train_args.etype.startswith("vgg"):
434
+ ss = train_args.subsample.split("_")
435
+ for j in range(
436
+ min(train_args.elayers_sd + train_args.elayers + 1, len(ss))
437
+ ):
438
+ subsample[j] = int(ss[j])
439
+ else:
440
+ logging.warning(
441
+ "Subsampling is not performed for vgg*. "
442
+ "It is performed in max pooling layers at CNN."
443
+ )
444
+ logging.info("subsample: " + " ".join([str(x) for x in subsample]))
445
+ return subsample
446
+
447
+ elif mode == "asr" and arch == "rnn_mulenc":
448
+ subsample_list = []
449
+ for idx in range(train_args.num_encs):
450
+ subsample = np.ones(train_args.elayers[idx] + 1, dtype=np.int)
451
+ if train_args.etype[idx].endswith("p") and not train_args.etype[
452
+ idx
453
+ ].startswith("vgg"):
454
+ ss = train_args.subsample[idx].split("_")
455
+ for j in range(min(train_args.elayers[idx] + 1, len(ss))):
456
+ subsample[j] = int(ss[j])
457
+ else:
458
+ logging.warning(
459
+ "Encoder %d: Subsampling is not performed for vgg*. "
460
+ "It is performed in max pooling layers at CNN.",
461
+ idx + 1,
462
+ )
463
+ logging.info("subsample: " + " ".join([str(x) for x in subsample]))
464
+ subsample_list.append(subsample)
465
+ return subsample_list
466
+
467
+ else:
468
+ raise ValueError("Invalid options: mode={}, arch={}".format(mode, arch))
469
+
470
+
471
+ def rename_state_dict(
472
+ old_prefix: str, new_prefix: str, state_dict: Dict[str, torch.Tensor]
473
+ ):
474
+ """Replace keys of old prefix with new prefix in state dict."""
475
+ # need this list not to break the dict iterator
476
+ old_keys = [k for k in state_dict if k.startswith(old_prefix)]
477
+ if len(old_keys) > 0:
478
+ logging.warning(f"Rename: {old_prefix} -> {new_prefix}")
479
+ for k in old_keys:
480
+ v = state_dict.pop(k)
481
+ new_k = k.replace(old_prefix, new_prefix)
482
+ state_dict[new_k] = v
483
+
484
+
485
+ def get_activation(act):
486
+ """Return activation function."""
487
+ # Lazy load to avoid unused import
488
+ from lscodec.models.conformer.swish import Swish
489
+
490
+ activation_funcs = {
491
+ "hardtanh": torch.nn.Hardtanh,
492
+ "tanh": torch.nn.Tanh,
493
+ "relu": torch.nn.ReLU,
494
+ "selu": torch.nn.SELU,
495
+ "swish": Swish,
496
+ }
497
+
498
+ return activation_funcs[act]()
lscodec/models/conformer/positionwise_feed_forward.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Shigeki Karita
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+ """Positionwise feed forward layer definition."""
8
+
9
+ import torch
10
+
11
+
12
+ class PositionwiseFeedForward(torch.nn.Module):
13
+ """Positionwise feed forward layer.
14
+
15
+ Args:
16
+ idim (int): Input dimenstion.
17
+ hidden_units (int): The number of hidden units.
18
+ dropout_rate (float): Dropout rate.
19
+
20
+ """
21
+
22
+ def __init__(self, idim, hidden_units, dropout_rate, activation=torch.nn.ReLU()):
23
+ """Construct an PositionwiseFeedForward object."""
24
+ super(PositionwiseFeedForward, self).__init__()
25
+ self.w_1 = torch.nn.Linear(idim, hidden_units)
26
+ self.w_2 = torch.nn.Linear(hidden_units, idim)
27
+ self.dropout = torch.nn.Dropout(dropout_rate)
28
+ self.activation = activation
29
+
30
+ def forward(self, x):
31
+ """Forward funciton."""
32
+ return self.w_2(self.dropout(self.activation(self.w_1(x))))
lscodec/models/conformer/repeat.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Shigeki Karita
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+ """Repeat the same layer definition."""
8
+
9
+ import torch
10
+
11
+
12
+ class MultiSequential(torch.nn.Sequential):
13
+ """Multi-input multi-output torch.nn.Sequential."""
14
+
15
+ def forward(self, *args):
16
+ """Repeat."""
17
+ for m in self:
18
+ args = m(*args)
19
+ return args
20
+
21
+
22
+ def repeat(N, fn):
23
+ """Repeat module N times.
24
+
25
+ Args:
26
+ N (int): Number of repeat time.
27
+ fn (Callable): Function to generate module.
28
+
29
+ Returns:
30
+ MultiSequential: Repeated model instance.
31
+
32
+ """
33
+ return MultiSequential(*[fn(n) for n in range(N)])
lscodec/models/conformer/subsampling.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2019 Shigeki Karita
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+ """Subsampling layer definition."""
8
+
9
+ import torch
10
+
11
+ from lscodec.models.conformer.embedding import PositionalEncoding
12
+
13
+
14
+ class TooShortUttError(Exception):
15
+ """Raised when the utt is too short for subsampling.
16
+
17
+ Args:
18
+ message (str): Message for error catch
19
+ actual_size (int): the short size that cannot pass the subsampling
20
+ limit (int): the limit size for subsampling
21
+
22
+ """
23
+
24
+ def __init__(self, message, actual_size, limit):
25
+ """Construct a TooShortUttError for error handler."""
26
+ super().__init__(message)
27
+ self.actual_size = actual_size
28
+ self.limit = limit
29
+
30
+
31
+ def check_short_utt(ins, size):
32
+ """Check if the utterance is too short for subsampling."""
33
+ if isinstance(ins, Conv2dSubsampling2) and size < 3:
34
+ return True, 3
35
+ if isinstance(ins, Conv2dSubsampling) and size < 7:
36
+ return True, 7
37
+ if isinstance(ins, Conv2dSubsampling6) and size < 11:
38
+ return True, 11
39
+ if isinstance(ins, Conv2dSubsampling8) and size < 15:
40
+ return True, 15
41
+ return False, -1
42
+
43
+
44
+ class Conv2dSubsampling(torch.nn.Module):
45
+ """Convolutional 2D subsampling (to 1/4 length).
46
+
47
+ Args:
48
+ idim (int): Input dimension.
49
+ odim (int): Output dimension.
50
+ dropout_rate (float): Dropout rate.
51
+ pos_enc (torch.nn.Module): Custom position encoding layer.
52
+
53
+ """
54
+
55
+ def __init__(self, idim, odim, dropout_rate, pos_enc=None):
56
+ """Construct an Conv2dSubsampling object."""
57
+ super(Conv2dSubsampling, self).__init__()
58
+ self.conv = torch.nn.Sequential(
59
+ torch.nn.Conv2d(1, odim, 3, 2),
60
+ torch.nn.ReLU(),
61
+ torch.nn.Conv2d(odim, odim, 3, 2),
62
+ torch.nn.ReLU(),
63
+ )
64
+ self.out = torch.nn.Sequential(
65
+ torch.nn.Linear(odim * (((idim - 1) // 2 - 1) // 2), odim),
66
+ pos_enc if pos_enc is not None else PositionalEncoding(odim, dropout_rate),
67
+ )
68
+
69
+ def forward(self, x, x_mask):
70
+ """Subsample x.
71
+
72
+ Args:
73
+ x (torch.Tensor): Input tensor (#batch, time, idim).
74
+ x_mask (torch.Tensor): Input mask (#batch, 1, time).
75
+
76
+ Returns:
77
+ torch.Tensor: Subsampled tensor (#batch, time', odim),
78
+ where time' = time // 4.
79
+ torch.Tensor: Subsampled mask (#batch, 1, time'),
80
+ where time' = time // 4.
81
+
82
+ """
83
+ x = x.unsqueeze(1) # (b, c, t, f)
84
+ x = self.conv(x)
85
+ b, c, t, f = x.size()
86
+ x = self.out(x.transpose(1, 2).contiguous().view(b, t, c * f))
87
+ if x_mask is None:
88
+ return x, None
89
+ return x, x_mask[:, :, :-2:2][:, :, :-2:2]
90
+
91
+ def __getitem__(self, key):
92
+ """Get item.
93
+
94
+ When reset_parameters() is called, if use_scaled_pos_enc is used,
95
+ return the positioning encoding.
96
+
97
+ """
98
+ if key != -1:
99
+ raise NotImplementedError("Support only `-1` (for `reset_parameters`).")
100
+ return self.out[key]
101
+
102
+
103
+ class Conv2dSubsampling2(torch.nn.Module):
104
+ """Convolutional 2D subsampling (to 1/2 length).
105
+
106
+ Args:
107
+ idim (int): Input dimension.
108
+ odim (int): Output dimension.
109
+ dropout_rate (float): Dropout rate.
110
+ pos_enc (torch.nn.Module): Custom position encoding layer.
111
+
112
+ """
113
+
114
+ def __init__(self, idim, odim, dropout_rate, pos_enc=None):
115
+ """Construct an Conv2dSubsampling2 object."""
116
+ super(Conv2dSubsampling2, self).__init__()
117
+ self.conv = torch.nn.Sequential(
118
+ torch.nn.Conv2d(1, odim, 3, 2),
119
+ torch.nn.ReLU(),
120
+ torch.nn.Conv2d(odim, odim, 3, 1),
121
+ torch.nn.ReLU(),
122
+ )
123
+ self.out = torch.nn.Sequential(
124
+ torch.nn.Linear(odim * (((idim - 1) // 2 - 2)), odim),
125
+ pos_enc if pos_enc is not None else PositionalEncoding(odim, dropout_rate),
126
+ )
127
+
128
+ def forward(self, x, x_mask):
129
+ """Subsample x.
130
+
131
+ Args:
132
+ x (torch.Tensor): Input tensor (#batch, time, idim).
133
+ x_mask (torch.Tensor): Input mask (#batch, 1, time).
134
+
135
+ Returns:
136
+ torch.Tensor: Subsampled tensor (#batch, time', odim),
137
+ where time' = time // 2.
138
+ torch.Tensor: Subsampled mask (#batch, 1, time'),
139
+ where time' = time // 2.
140
+
141
+ """
142
+ x = x.unsqueeze(1) # (b, c, t, f)
143
+ x = self.conv(x)
144
+ b, c, t, f = x.size()
145
+ x = self.out(x.transpose(1, 2).contiguous().view(b, t, c * f))
146
+ if x_mask is None:
147
+ return x, None
148
+ return x, x_mask[:, :, :-2:2][:, :, :-2:1]
149
+
150
+ def __getitem__(self, key):
151
+ """Get item.
152
+
153
+ When reset_parameters() is called, if use_scaled_pos_enc is used,
154
+ return the positioning encoding.
155
+
156
+ """
157
+ if key != -1:
158
+ raise NotImplementedError("Support only `-1` (for `reset_parameters`).")
159
+ return self.out[key]
160
+
161
+
162
+ class Conv2dSubsampling6(torch.nn.Module):
163
+ """Convolutional 2D subsampling (to 1/6 length).
164
+
165
+ Args:
166
+ idim (int): Input dimension.
167
+ odim (int): Output dimension.
168
+ dropout_rate (float): Dropout rate.
169
+ pos_enc (torch.nn.Module): Custom position encoding layer.
170
+
171
+ """
172
+
173
+ def __init__(self, idim, odim, dropout_rate, pos_enc=None):
174
+ """Construct an Conv2dSubsampling6 object."""
175
+ super(Conv2dSubsampling6, self).__init__()
176
+ self.conv = torch.nn.Sequential(
177
+ torch.nn.Conv2d(1, odim, 3, 2),
178
+ torch.nn.ReLU(),
179
+ torch.nn.Conv2d(odim, odim, 5, 3),
180
+ torch.nn.ReLU(),
181
+ )
182
+ self.out = torch.nn.Sequential(
183
+ torch.nn.Linear(odim * (((idim - 1) // 2 - 2) // 3), odim),
184
+ pos_enc if pos_enc is not None else PositionalEncoding(odim, dropout_rate),
185
+ )
186
+
187
+ def forward(self, x, x_mask):
188
+ """Subsample x.
189
+
190
+ Args:
191
+ x (torch.Tensor): Input tensor (#batch, time, idim).
192
+ x_mask (torch.Tensor): Input mask (#batch, 1, time).
193
+
194
+ Returns:
195
+ torch.Tensor: Subsampled tensor (#batch, time', odim),
196
+ where time' = time // 6.
197
+ torch.Tensor: Subsampled mask (#batch, 1, time'),
198
+ where time' = time // 6.
199
+
200
+ """
201
+ x = x.unsqueeze(1) # (b, c, t, f)
202
+ x = self.conv(x)
203
+ b, c, t, f = x.size()
204
+ x = self.out(x.transpose(1, 2).contiguous().view(b, t, c * f))
205
+ if x_mask is None:
206
+ return x, None
207
+ return x, x_mask[:, :, :-2:2][:, :, :-4:3]
208
+
209
+
210
+ class Conv2dSubsampling8(torch.nn.Module):
211
+ """Convolutional 2D subsampling (to 1/8 length).
212
+
213
+ Args:
214
+ idim (int): Input dimension.
215
+ odim (int): Output dimension.
216
+ dropout_rate (float): Dropout rate.
217
+ pos_enc (torch.nn.Module): Custom position encoding layer.
218
+
219
+ """
220
+
221
+ def __init__(self, idim, odim, dropout_rate, pos_enc=None):
222
+ """Construct an Conv2dSubsampling8 object."""
223
+ super(Conv2dSubsampling8, self).__init__()
224
+ self.conv = torch.nn.Sequential(
225
+ torch.nn.Conv2d(1, odim, 3, 2),
226
+ torch.nn.ReLU(),
227
+ torch.nn.Conv2d(odim, odim, 3, 2),
228
+ torch.nn.ReLU(),
229
+ torch.nn.Conv2d(odim, odim, 3, 2),
230
+ torch.nn.ReLU(),
231
+ )
232
+ self.out = torch.nn.Sequential(
233
+ torch.nn.Linear(odim * ((((idim - 1) // 2 - 1) // 2 - 1) // 2), odim),
234
+ pos_enc if pos_enc is not None else PositionalEncoding(odim, dropout_rate),
235
+ )
236
+
237
+ def forward(self, x, x_mask):
238
+ """Subsample x.
239
+
240
+ Args:
241
+ x (torch.Tensor): Input tensor (#batch, time, idim).
242
+ x_mask (torch.Tensor): Input mask (#batch, 1, time).
243
+
244
+ Returns:
245
+ torch.Tensor: Subsampled tensor (#batch, time', odim),
246
+ where time' = time // 8.
247
+ torch.Tensor: Subsampled mask (#batch, 1, time'),
248
+ where time' = time // 8.
249
+
250
+ """
251
+ x = x.unsqueeze(1) # (b, c, t, f)
252
+ x = self.conv(x)
253
+ b, c, t, f = x.size()
254
+ x = self.out(x.transpose(1, 2).contiguous().view(b, t, c * f))
255
+ if x_mask is None:
256
+ return x, None
257
+ return x, x_mask[:, :, :-2:2][:, :, :-2:2][:, :, :-2:2]
lscodec/models/conformer/swish.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2020 Johns Hopkins University (Shinji Watanabe)
5
+ # Northwestern Polytechnical University (Pengcheng Guo)
6
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
7
+
8
+ """Swish() activation function for Conformer."""
9
+
10
+ import torch
11
+
12
+
13
+ class Swish(torch.nn.Module):
14
+ """Construct an Swish object."""
15
+
16
+ def forward(self, x):
17
+ """Return Swich activation function."""
18
+ return x * torch.sigmoid(x)
lscodec/models/conformer/vgg2l.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """VGG2L module definition for transformer encoder."""
2
+
3
+ from typing import Tuple
4
+ from typing import Union
5
+
6
+ import torch
7
+
8
+
9
+ class VGG2L(torch.nn.Module):
10
+ """VGG2L module for custom encoder.
11
+
12
+ Args:
13
+ idim: Dimension of inputs
14
+ odim: Dimension of outputs
15
+ pos_enc: Positional encoding class
16
+
17
+ """
18
+
19
+ def __init__(self, idim: int, odim: int, pos_enc: torch.nn.Module = None):
20
+ """Construct a VGG2L object."""
21
+ super().__init__()
22
+
23
+ self.vgg2l = torch.nn.Sequential(
24
+ torch.nn.Conv2d(1, 64, 3, stride=1, padding=1),
25
+ torch.nn.ReLU(),
26
+ torch.nn.Conv2d(64, 64, 3, stride=1, padding=1),
27
+ torch.nn.ReLU(),
28
+ torch.nn.MaxPool2d((3, 2)),
29
+ torch.nn.Conv2d(64, 128, 3, stride=1, padding=1),
30
+ torch.nn.ReLU(),
31
+ torch.nn.Conv2d(128, 128, 3, stride=1, padding=1),
32
+ torch.nn.ReLU(),
33
+ torch.nn.MaxPool2d((2, 2)),
34
+ )
35
+
36
+ if pos_enc is not None:
37
+ self.output = torch.nn.Sequential(
38
+ torch.nn.Linear(128 * ((idim // 2) // 2), odim), pos_enc
39
+ )
40
+ else:
41
+ self.output = torch.nn.Linear(128 * ((idim // 2) // 2), odim)
42
+
43
+ def forward(
44
+ self, x: torch.Tensor, x_mask: torch.Tensor
45
+ ) -> Union[
46
+ Tuple[torch.Tensor, torch.Tensor],
47
+ Tuple[Tuple[torch.Tensor, torch.Tensor], torch.Tensor],
48
+ ]:
49
+ """VGG2L forward for x.
50
+
51
+ Args:
52
+ x: Input tensor (B, T, idim)
53
+ x_mask: Input mask (B, 1, T)
54
+
55
+ Returns:
56
+ x: Output tensor (B, sub(T), odim)
57
+ or ((B, sub(T), odim), (B, sub(T), att_dim))
58
+ x_mask: Output mask (B, 1, sub(T))
59
+
60
+ """
61
+ x = x.unsqueeze(1)
62
+ x = self.vgg2l(x)
63
+
64
+ b, c, t, f = x.size()
65
+
66
+ x = self.output(x.transpose(1, 2).contiguous().view(b, t, c * f))
67
+
68
+ if x_mask is not None:
69
+ x_mask = self.create_new_mask(x_mask)
70
+
71
+ return x, x_mask
72
+
73
+ def create_new_mask(self, x_mask: torch.Tensor) -> torch.Tensor:
74
+ """Create a subsampled version of x_mask.
75
+
76
+ Args:
77
+ x_mask: Input mask (B, 1, T)
78
+
79
+ Returns:
80
+ x_mask: Output mask (B, 1, sub(T))
81
+
82
+ """
83
+ x_t1 = x_mask.size(2) - (x_mask.size(2) % 3)
84
+ x_mask = x_mask[:, :, :x_t1][:, :, ::3]
85
+
86
+ x_t2 = x_mask.size(2) - (x_mask.size(2) % 2)
87
+ x_mask = x_mask[:, :, :x_t2][:, :, ::2]
88
+
89
+ return x_mask
lscodec/models/ctx_v2w.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ # MIT License
3
+ # Copyright (c) 2025 Yiwei Guo
4
+
5
+ import torch
6
+ from typing import Union
7
+ from lscodec.models.conformer.decoder import Decoder as ConformerDecoder
8
+
9
+ from lscodec.utils import crop_seq
10
+
11
+ from lscodec.models.prompt_prenet import ConvPromptPrenet
12
+ from lscodec.models.hifigan import HiFiGANGenerator
13
+ import logging
14
+
15
+
16
+ class CTXVEC2WAVFrontend(torch.nn.Module):
17
+
18
+ def __init__(self,
19
+ prompt_net_type,
20
+ num_mels,
21
+ vqvec_channels,
22
+ prompt_channels,
23
+ conformer_params):
24
+
25
+ super(CTXVEC2WAVFrontend, self).__init__()
26
+
27
+ if prompt_net_type == "ConvPromptPrenet":
28
+ self.prompt_prenet = ConvPromptPrenet(
29
+ embed=prompt_channels,
30
+ conv_layers=[(128, 3, 1, 1), (256, 5, 1, 2), (512, 5, 1, 2), (conformer_params["attention_dim"], 3, 1, 1)],
31
+ dropout=0.1,
32
+ skip_connections=True,
33
+ residual_scale=0.25,
34
+ non_affine_group_norm=False,
35
+ conv_bias=True,
36
+ activation=torch.nn.ReLU()
37
+ )
38
+ elif prompt_net_type == "Conv1d":
39
+ self.prompt_prenet = torch.nn.Conv1d(prompt_channels, conformer_params["attention_dim"], kernel_size=5, padding=2) # , padding=2)
40
+ else:
41
+ raise NotImplementedError
42
+
43
+ self.input_dim = vqvec_channels
44
+ self.encoder1 = ConformerDecoder(vqvec_channels, input_layer='linear', **conformer_params)
45
+
46
+ self.hidden_proj = torch.nn.Linear(conformer_params["attention_dim"], conformer_params["attention_dim"])
47
+
48
+ self.encoder2 = ConformerDecoder(0, input_layer=None, **conformer_params)
49
+ self.mel_proj = torch.nn.Linear(conformer_params["attention_dim"], num_mels)
50
+
51
+ def forward(self, vqvec, prompt, mask=None, prompt_mask=None):
52
+ """
53
+ params:
54
+ vqvec: sequence of VQ-vectors.
55
+ prompt: sequence of mel-spectrogram prompt (acoustic context)
56
+ mask: mask of the vqvec. True or 1 stands for valid values.
57
+ prompt_mask: mask of the prompt.
58
+ vqvec and prompt are of shape [B, D, T]. All masks are of shape [B, T].
59
+ returns:
60
+ enc_out: the input to the CTX-vec2wav Generator (HifiGAN);
61
+ mel: the frontend predicted mel spectrogram (for faster convergence);
62
+ """
63
+ prompt = self.prompt_prenet(prompt.transpose(1, 2)).transpose(1, 2)
64
+
65
+ if mask is not None:
66
+ mask = mask.unsqueeze(-2)
67
+ if prompt_mask is not None:
68
+ prompt_mask = prompt_mask.unsqueeze(-2)
69
+ enc_out, _ = self.encoder1(vqvec, mask, prompt, prompt_mask)
70
+
71
+ h = self.hidden_proj(enc_out)
72
+
73
+ enc_out, _ = self.encoder2(h, mask, prompt, prompt_mask)
74
+ mel = self.mel_proj(enc_out) # (B, L, 80)
75
+
76
+ return enc_out, mel
77
+
78
+
79
+ class CTXVEC2WAVGenerator(torch.nn.Module):
80
+
81
+ def __init__(self, frontend: CTXVEC2WAVFrontend, backend: HiFiGANGenerator):
82
+
83
+ super(CTXVEC2WAVGenerator, self).__init__()
84
+ self.frontend = frontend
85
+ self.backend = backend
86
+
87
+ def forward(self, vqvec, prompt, mask=None, prompt_mask=None, crop_len=0, crop_offsets=None):
88
+ """
89
+ :param vqvec: (torch.Tensor) The shape is (B, L, D). Sequence of VQ-vectors.
90
+ :param prompt: (torch.Tensor) The shape is (B, L', D'). Sequence of prompt features (acoustic context)
91
+ :param mask: (torch.Tensor) The dtype is torch.bool. The shape is (B, L). True or 1 stands for valid values in `vqvec`.
92
+ :param prompt_mask: (torch.Tensor) The dtype is torch.bool. The shape is (B, L'). True or 1 stands for valid values in `prompt`.
93
+ :return: frontend predicted mel spectrogram; reconstructed waveform.
94
+ """
95
+ h, mel, _ = self.frontend(vqvec, prompt, mask=mask, prompt_mask=prompt_mask) # (B, L, adim), (B, L, 80)
96
+ if mask is not None:
97
+ h = h.masked_fill(~mask.unsqueeze(-1), 0)
98
+ h = h.transpose(1, 2)
99
+ if crop_len > 0:
100
+ h = crop_seq(h, crop_offsets, crop_len)
101
+ wav = self.backend(h) # (B, C, T)
102
+ return mel, None, wav
103
+
104
+ def inference(self, vqvec, prompt, **kwargs):
105
+ h, mel = self.frontend(vqvec, prompt)
106
+ wav = self.backend.inference(h, **kwargs)
107
+
108
+ return mel, None, wav
lscodec/models/fairseq_modules/__init__.py ADDED
File without changes
lscodec/models/fairseq_modules/fp32_group_norm.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+ """
6
+ Layer norm done in fp32 (for fp16 training)
7
+ """
8
+
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+ import torch
12
+
13
+ from math import ceil, floor
14
+
15
+ import torch
16
+
17
+
18
+ def torch_quantile(
19
+ input: torch.Tensor,
20
+ q: float | torch.Tensor,
21
+ dim: int | None = None,
22
+ keepdim: bool = False,
23
+ *,
24
+ interpolation: str = "nearest",
25
+ out: torch.Tensor | None = None,
26
+ ) -> torch.Tensor:
27
+ """Better torch.quantile for one SCALAR quantile.
28
+
29
+ Using torch.kthvalue. Better than torch.quantile because:
30
+ - No 2**24 input size limit (pytorch/issues/67592),
31
+ - Much faster, at least on big input sizes.
32
+
33
+ Arguments:
34
+ input (torch.Tensor): See torch.quantile.
35
+ q (float): See torch.quantile. Supports only scalar input
36
+ currently.
37
+ dim (int | None): See torch.quantile.
38
+ keepdim (bool): See torch.quantile. Supports only False
39
+ currently.
40
+ interpolation: {"nearest", "lower", "higher"}
41
+ See torch.quantile.
42
+ out (torch.Tensor | None): See torch.quantile. Supports only
43
+ None currently.
44
+ """
45
+ # Sanitization: q
46
+ try:
47
+ q = float(q)
48
+ assert 0 <= q <= 1
49
+ except Exception:
50
+ raise ValueError(f"Only scalar input 0<=q<=1 is currently supported (got {q})!")
51
+
52
+ # Sanitization: dim
53
+ # Because one cannot pass `dim=None` to `squeeze()` or `kthvalue()`
54
+ if dim_was_none := dim is None:
55
+ dim = 0
56
+ input = input.reshape((-1,) + (1,) * (input.ndim - 1))
57
+
58
+ # Sanitization: inteporlation
59
+ if interpolation == "nearest":
60
+ inter = round
61
+ elif interpolation == "lower":
62
+ inter = floor
63
+ elif interpolation == "higher":
64
+ inter = ceil
65
+ else:
66
+ raise ValueError(
67
+ "Supported interpolations currently are {'nearest', 'lower', 'higher'} "
68
+ f"(got '{interpolation}')!"
69
+ )
70
+
71
+ # Sanitization: out
72
+ if out is not None:
73
+ raise ValueError(f"Only None value is currently supported for out (got {out})!")
74
+
75
+ # Logic
76
+ k = inter(q * (input.shape[dim] - 1)) + 1
77
+ out = torch.kthvalue(input, k, dim, keepdim=True, out=out)[0]
78
+
79
+ # Rectification: keepdim
80
+ if keepdim:
81
+ return out
82
+ if dim_was_none:
83
+ return out.squeeze()
84
+ else:
85
+ return out.squeeze(dim)
86
+
87
+ return out
88
+
89
+ class Fp32GroupNorm(nn.GroupNorm):
90
+ def __init__(self, *args, **kwargs):
91
+ super().__init__(*args, **kwargs)
92
+
93
+ def forward(self, input):
94
+ # print("In FP32 GroupNorm", input[0].mean(), input[0].std(), input[0].min(), input[0].max())
95
+ output = F.group_norm(
96
+ input.float(),
97
+ self.num_groups,
98
+ self.weight.float() if self.weight is not None else None,
99
+ self.bias.float() if self.bias is not None else None,
100
+ self.eps,
101
+ )
102
+
103
+ # NOTE: dirty hack
104
+ # if input.max()<1000:
105
+ # output = F.group_norm(
106
+ # input.float(),
107
+ # self.num_groups,
108
+ # self.weight.float() if self.weight is not None else None,
109
+ # self.bias.float() if self.bias is not None else None,
110
+ # self.eps,
111
+ # )
112
+ # else:
113
+ # print("Will remove outliers in a dirty way in FP32GroupNorm")
114
+ # q1 = torch_quantile(input.view(-1), 0.25)
115
+ # q3 = torch_quantile(input.view(-1), 0.75)
116
+ # IQR = q3-q1
117
+ # upper_bound = q3 + 1.5 * IQR
118
+ # lower_bound = q1 - 1.5 * IQR
119
+ # # correct_mean = input[(lower_bound<input) & (input<upper_bound)].mean()
120
+ # # correct_std = input[(lower_bound<input) & (input<upper_bound)].std()
121
+ # # print(f"After removing outliers, the correct mean and std are {correct_mean}, {correct_std}")
122
+ # # output = (input-correct_mean)/(correct_std+self.eps) * self.weight.float().unsqueeze(0).unsqueeze(-1) + self.bias.unsqueeze(0).unsqueeze(-1)
123
+
124
+ # input[(lower_bound<input) & (input<upper_bound)] = 0.0
125
+ # output = F.group_norm(
126
+ # input.float(),
127
+ # self.num_groups,
128
+ # self.weight.float() if self.weight is not None else None,
129
+ # self.bias.float() if self.bias is not None else None,
130
+ # self.eps,
131
+ # )
132
+
133
+ return output.type_as(input)
lscodec/models/fairseq_modules/gumbel_vector_quantizer.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+
10
+
11
+ class GumbelVectorQuantizer(nn.Module):
12
+ def __init__(
13
+ self,
14
+ dim,
15
+ num_vars,
16
+ temp,
17
+ groups,
18
+ combine_groups,
19
+ vq_dim,
20
+ time_first,
21
+ activation=nn.GELU(),
22
+ weight_proj_depth=1,
23
+ weight_proj_factor=1,
24
+ hard=True,
25
+ std=0,
26
+ ):
27
+ """Vector quantization using gumbel softmax
28
+
29
+ Args:
30
+ dim: input dimension (channels)
31
+ num_vars: number of quantized vectors per group
32
+ temp: temperature for training. this should be a tuple of 3 elements: (start, stop, decay factor)
33
+ groups: number of groups for vector quantization
34
+ combine_groups: whether to use the vectors for all groups
35
+ vq_dim: dimensionality of the resulting quantized vector
36
+ time_first: if true, expect input in BxTxC format, otherwise in BxCxT
37
+ activation: what activation to use (should be a module). this is only used if weight_proj_depth is > 1
38
+ weight_proj_depth: number of layers (with activation in between) to project input before computing logits
39
+ weight_proj_factor: this is used only if weight_proj_depth is > 1. scales the inner dimensionality of
40
+ projections by this factor
41
+ """
42
+ super().__init__()
43
+
44
+ self.groups = groups
45
+ self.combine_groups = combine_groups
46
+ self.input_dim = dim
47
+ self.num_vars = num_vars
48
+ self.time_first = time_first
49
+ self.hard = hard
50
+
51
+ assert (
52
+ vq_dim % groups == 0
53
+ ), f"dim {vq_dim} must be divisible by groups {groups} for concatenation"
54
+
55
+ var_dim = vq_dim // groups
56
+ num_groups = groups if not combine_groups else 1
57
+
58
+ self.vars = nn.Parameter(torch.FloatTensor(1, num_groups * num_vars, var_dim))
59
+ if std == 0:
60
+ nn.init.uniform_(self.vars)
61
+ else:
62
+ nn.init.normal_(self.vars, mean=0, std=std)
63
+
64
+ if weight_proj_depth > 1:
65
+
66
+ def block(input_dim, output_dim):
67
+ return nn.Sequential(nn.Linear(input_dim, output_dim), activation)
68
+
69
+ inner_dim = self.input_dim * weight_proj_factor
70
+ self.weight_proj = nn.Sequential(
71
+ *[
72
+ block(self.input_dim if i == 0 else inner_dim, inner_dim)
73
+ for i in range(weight_proj_depth - 1)
74
+ ],
75
+ nn.Linear(inner_dim, groups * num_vars),
76
+ )
77
+ else:
78
+ self.weight_proj = nn.Linear(self.input_dim, groups * num_vars)
79
+ nn.init.normal_(self.weight_proj.weight, mean=0, std=1)
80
+ nn.init.zeros_(self.weight_proj.bias)
81
+
82
+ if isinstance(temp, str):
83
+ import ast
84
+
85
+ temp = ast.literal_eval(temp)
86
+ assert len(temp) == 3, f"{temp}, {len(temp)}"
87
+
88
+ self.max_temp, self.min_temp, self.temp_decay = temp
89
+ self.curr_temp = self.max_temp
90
+ self.codebook_indices = None
91
+
92
+ def set_num_updates(self, num_updates):
93
+ self.curr_temp = max(
94
+ self.max_temp * self.temp_decay**num_updates, self.min_temp
95
+ )
96
+
97
+ def get_codebook_indices(self):
98
+ if self.codebook_indices is None:
99
+ from itertools import product
100
+
101
+ p = [range(self.num_vars)] * self.groups
102
+ inds = list(product(*p))
103
+ self.codebook_indices = torch.tensor(
104
+ inds, dtype=torch.long, device=self.vars.device
105
+ ).flatten()
106
+
107
+ if not self.combine_groups:
108
+ self.codebook_indices = self.codebook_indices.view(
109
+ self.num_vars**self.groups, -1
110
+ )
111
+ for b in range(1, self.groups):
112
+ self.codebook_indices[:, b] += self.num_vars * b
113
+ self.codebook_indices = self.codebook_indices.flatten()
114
+ return self.codebook_indices
115
+
116
+ def codebook(self):
117
+ indices = self.get_codebook_indices()
118
+ return (
119
+ self.vars.squeeze(0)
120
+ .index_select(0, indices)
121
+ .view(self.num_vars**self.groups, -1)
122
+ )
123
+
124
+ def sample_from_codebook(self, b, n):
125
+ indices = self.get_codebook_indices()
126
+ indices = indices.view(-1, self.groups)
127
+ cb_size = indices.size(0)
128
+ assert (
129
+ n < cb_size
130
+ ), f"sample size {n} is greater than size of codebook {cb_size}"
131
+ sample_idx = torch.randint(low=0, high=cb_size, size=(b * n,))
132
+ indices = indices[sample_idx]
133
+
134
+ z = self.vars.squeeze(0).index_select(0, indices.flatten()).view(b, n, -1)
135
+ return z
136
+
137
+ def to_codebook_index(self, indices):
138
+ res = indices.new_full(indices.shape[:-1], 0)
139
+ for i in range(self.groups):
140
+ exponent = self.groups - i - 1
141
+ res += indices[..., i] * (self.num_vars**exponent)
142
+ return res
143
+
144
+ def forward_idx(self, x):
145
+ res = self.forward(x, produce_targets=True)
146
+ return res["x"], res["targets"]
147
+
148
+ def forward(self, x, produce_targets=True):
149
+
150
+ result = {"num_vars": self.num_vars * self.groups}
151
+
152
+ # if not self.time_first:
153
+ # x = x.transpose(1, 2)
154
+
155
+ bsz_tsz, fsz = x.shape
156
+ # x = x.reshape(-1, fsz)
157
+ x = self.weight_proj(x)
158
+ x = x.view(bsz_tsz * self.groups, -1)
159
+
160
+ with torch.no_grad():
161
+ _, k = x.max(-1)
162
+ hard_x = (
163
+ x.new_zeros(*x.shape)
164
+ .scatter_(-1, k.view(-1, 1), 1.0)
165
+ .view(bsz_tsz, self.groups, -1)
166
+ )
167
+ hard_probs = torch.mean(hard_x.float(), dim=0)
168
+ result["code_perplexity"] = torch.exp(
169
+ -torch.sum(hard_probs * torch.log(hard_probs + 1e-7), dim=-1)
170
+ ).sum()
171
+
172
+ avg_probs = torch.softmax(
173
+ x.view(bsz_tsz, self.groups, -1).float(), dim=-1
174
+ ).mean(dim=0)
175
+ result["prob_perplexity"] = torch.exp(
176
+ -torch.sum(avg_probs * torch.log(avg_probs + 1e-7), dim=-1)
177
+ ).sum()
178
+
179
+ result["temp"] = self.curr_temp
180
+
181
+ if self.training:
182
+ x = F.gumbel_softmax(x.float(), tau=self.curr_temp, hard=self.hard).type_as(
183
+ x
184
+ )
185
+ else:
186
+ x = hard_x
187
+
188
+ x = x.view(bsz_tsz, -1)
189
+
190
+ vars = self.vars
191
+ if self.combine_groups:
192
+ vars = vars.repeat(1, self.groups, 1)
193
+
194
+ if produce_targets:
195
+ result["targets"] = (
196
+ x.view(bsz_tsz * self.groups, -1)
197
+ .argmax(dim=-1)
198
+ .detach()
199
+ )
200
+
201
+ x = x.unsqueeze(-1) * vars
202
+ x = x.view(bsz_tsz, self.groups, self.num_vars, -1)
203
+ x = x.sum(-2)
204
+ x = x.view(bsz_tsz, -1)
205
+
206
+ # if not self.time_first:
207
+ # x = x.transpose(1, 2) # BTC -> BCT
208
+
209
+ result["x"] = x
210
+
211
+ # return result
212
+ return x, result['targets'], None
lscodec/models/fairseq_modules/kmeans_vector_quantizer.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ from lscodec.models.fairseq_modules.fp32_group_norm import Fp32GroupNorm
9
+
10
+
11
+ class KmeansVectorQuantizer(nn.Module):
12
+ def __init__(
13
+ self, dim, num_vars, groups, combine_groups, vq_dim, time_first, gamma=0.25
14
+ ):
15
+ """Vector quantization using straight pass-through estimator (i.e. kmeans)
16
+
17
+ Args:
18
+ dim: input dimension (channels)
19
+ num_vars: number of quantized vectors per group
20
+ groups: number of groups for vector quantization
21
+ combine_groups: whether to use the vectors for all groups
22
+ vq_dim: dimensionality of the resulting quantized vector
23
+ time_first: if true, expect input in BxTxC format, otherwise in BxCxT
24
+ gamma: commitment loss coefficient
25
+ """
26
+ super().__init__()
27
+
28
+ self.groups = groups
29
+ self.combine_groups = combine_groups
30
+ self.input_dim = dim
31
+ self.num_vars = num_vars
32
+ self.vq_dim = vq_dim
33
+ self.time_first = time_first
34
+
35
+ assert (
36
+ vq_dim % groups == 0
37
+ ), f"dim {vq_dim} must be divisible by groups {groups} for concatenation"
38
+
39
+ self.var_dim = vq_dim // groups
40
+ num_groups = groups if not combine_groups else 1
41
+
42
+ self.embedding = nn.Parameter(
43
+ 0.01 * torch.randn(num_vars, num_groups, self.var_dim)
44
+ )
45
+ self.projection = nn.Sequential(
46
+ nn.Conv1d(dim, dim, kernel_size=1, groups=groups, bias=False),
47
+ Fp32GroupNorm(groups, dim),
48
+ )
49
+ self.gamma = gamma
50
+ self.mse_mean = nn.MSELoss(reduction="mean")
51
+
52
+ def _pass_grad(self, x, y):
53
+ """Manually set gradient for backward pass.
54
+ for y = f(x), ensure that during the backward pass,
55
+ dL/dy = dL/dx regardless of f(x).
56
+ Returns:
57
+ y, with the gradient forced to be dL/dy = dL/dx.
58
+ """
59
+
60
+ return y.detach() + (x - x.detach())
61
+
62
+ @property
63
+ def expand_embedding(self):
64
+ if self.combine_groups:
65
+ return self.embedding.expand(self.num_vars, self.groups, self.var_dim)
66
+ return self.embedding
67
+
68
+ def forward_idx(self, x):
69
+ res = self.forward(x, produce_targets=True)
70
+ return res["x"], res["targets"]
71
+
72
+ def forward_idx_limited(self, x, valid_label2vqidx_mat):
73
+ # mask_mat = convert_valid_label2vqidx_to_mask_mat(valid_label2vqidx)
74
+ res = self.forward_group2(x, mask_mat=valid_label2vqidx_mat, produce_targets=True)
75
+ return res['x'], res['targets']
76
+
77
+ def forward(self, x, produce_targets=False):
78
+
79
+ result = {"num_vars": self.num_vars}
80
+
81
+ if self.time_first:
82
+ x = x.transpose(1, 2)
83
+
84
+ bsz, fsz, tsz = x.shape
85
+
86
+ ze = self.projection(x)
87
+ ze_ = ze.view(bsz, self.groups, self.var_dim, tsz).permute(0, 3, 1, 2)
88
+ d = (
89
+ (ze_.unsqueeze(0) - self.expand_embedding.unsqueeze(1).unsqueeze(1))
90
+ .view(self.num_vars, bsz, tsz, self.groups, -1)
91
+ .norm(dim=-1, p=2)
92
+ )
93
+ idx = d.argmin(dim=0)
94
+ zq = (
95
+ torch.stack(
96
+ [
97
+ self.expand_embedding[idx[..., group], group]
98
+ for group in range(self.groups)
99
+ ],
100
+ dim=-2,
101
+ )
102
+ .view(bsz, tsz, self.groups * self.var_dim)
103
+ .permute(0, 2, 1)
104
+ )
105
+ assert ze.shape == zq.shape, (ze.shape, zq.shape)
106
+ x = self._pass_grad(ze, zq)
107
+
108
+ with torch.no_grad():
109
+ hard_x = (
110
+ idx.new_zeros(bsz * tsz * self.groups, self.num_vars)
111
+ .scatter_(-1, idx.view(-1, 1), 1.0)
112
+ .view(bsz * tsz, self.groups, -1)
113
+ )
114
+ hard_probs = torch.mean(hard_x.float(), dim=0)
115
+ result["code_perplexity"] = torch.exp(
116
+ -torch.sum(hard_probs * torch.log(hard_probs + 1e-7), dim=-1)
117
+ ).sum()
118
+
119
+ if produce_targets:
120
+ result["targets"] = idx
121
+
122
+ if self.time_first:
123
+ x = x.transpose(1, 2) # BCT -> BTC
124
+ result["x"] = x
125
+
126
+ ze = ze.float()
127
+ zq = zq.float()
128
+ latent_loss = self.mse_mean(zq, ze.detach())
129
+ commitment_loss = self.mse_mean(ze, zq.detach())
130
+
131
+ result["kmeans_loss"] = latent_loss + self.gamma * commitment_loss
132
+
133
+ return result
134
+
135
+ def forward_group2(self, x, mask_mat=None, produce_targets=False, inf=999999):
136
+ assert mask_mat is not None
137
+
138
+ result = {"num_vars": self.num_vars}
139
+
140
+ if self.time_first:
141
+ x = x.transpose(1, 2)
142
+
143
+ bsz, fsz, tsz = x.shape
144
+
145
+ ze = self.projection(x)
146
+
147
+ ze_ = ze.view(bsz, self.groups, self.var_dim, tsz).permute(0, 3, 1, 2)
148
+ ze_0 = ze_[:, :, 0, None, :]
149
+ ze_1 = ze_[:, :, 1, None, :] # 4 * 100 * 320 * 128
150
+ cb0_expand = self.expand_embedding[:, 0, :]
151
+ cb1_expand = self.expand_embedding[:, 1, :] # 320 * 128
152
+ dist_0 = ((ze_0 - cb0_expand) ** 2).sum(dim=-1)[:, :, :, None]
153
+ dist_1 = ((ze_1 - cb1_expand) ** 2).sum(dim=-1)[:, :, None, :]
154
+ res_0, res_1 = torch.broadcast_tensors(dist_0, dist_1)
155
+ mask_mat = (1 - mask_mat[None, None, :, :].to(res_0.device) * torch.ones_like(res_0)) * inf
156
+ # mask_mat = mask_mat.to(x.device)
157
+ d_flt = (res_0 + res_1 + mask_mat).view(bsz, tsz, -1)
158
+ idx_flt = torch.argmin(d_flt, dim=-1)
159
+ idx = torch.stack((idx_flt // self.num_vars, idx_flt % self.num_vars), dim=-1)
160
+
161
+ zq = (
162
+ torch.stack(
163
+ [
164
+ self.expand_embedding[idx[..., group], group]
165
+ for group in range(self.groups)
166
+ ],
167
+ dim=-2,
168
+ )
169
+ .view(bsz, tsz, self.groups * self.var_dim)
170
+ .permute(0, 2, 1)
171
+ )
172
+ assert ze.shape == zq.shape, (ze.shape, zq.shape)
173
+ x = self._pass_grad(ze, zq)
174
+
175
+ with torch.no_grad():
176
+ hard_x = (
177
+ idx.new_zeros(bsz * tsz * self.groups, self.num_vars)
178
+ .scatter_(-1, idx.view(-1, 1), 1.0)
179
+ .view(bsz * tsz, self.groups, -1)
180
+ )
181
+ hard_probs = torch.mean(hard_x.float(), dim=0)
182
+ result["code_perplexity"] = torch.exp(
183
+ -torch.sum(hard_probs * torch.log(hard_probs + 1e-7), dim=-1)
184
+ ).sum()
185
+
186
+ if produce_targets:
187
+ result["targets"] = idx
188
+
189
+ if self.time_first:
190
+ x = x.transpose(1, 2) # BCT -> BTC
191
+ result["x"] = x
192
+
193
+ ze = ze.float()
194
+ zq = zq.float()
195
+ latent_loss = self.mse_mean(zq, ze.detach())
196
+ commitment_loss = self.mse_mean(ze, zq.detach())
197
+
198
+ result["kmeans_loss"] = latent_loss + self.gamma * commitment_loss
199
+
200
+ return result
201
+
202
+ if __name__ == "__main__":
203
+ quantizer = KmeansVectorQuantizer(dim=256, num_vars=320, groups=2, combine_groups=False, vq_dim=256, time_first=True)
204
+ x = torch.ones(4, 100, 256)
205
+ result = quantizer.forward_group2(x, mask_mat=torch.randint(0, 2, (320, 320)))
206
+ print(result)
lscodec/models/fairseq_modules/layer_norm.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+
10
+ try:
11
+ from apex.normalization import FusedLayerNorm as _FusedLayerNorm
12
+
13
+ has_fused_layernorm = True
14
+
15
+ class FusedLayerNorm(_FusedLayerNorm):
16
+ @torch.jit.unused
17
+ def forward(self, x):
18
+ if not x.is_cuda:
19
+ return super().forward(x)
20
+ else:
21
+ with torch.cuda.device(x.device):
22
+ return super().forward(x)
23
+
24
+ except ImportError:
25
+ has_fused_layernorm = False
26
+
27
+
28
+ def LayerNorm(normalized_shape, eps=1e-5, elementwise_affine=True, export=False):
29
+ if torch.jit.is_scripting() or torch.jit.is_tracing():
30
+ export = True
31
+ if not export and torch.cuda.is_available() and has_fused_layernorm:
32
+ return FusedLayerNorm(normalized_shape, eps, elementwise_affine)
33
+ return torch.nn.LayerNorm(normalized_shape, eps, elementwise_affine)
34
+
35
+
36
+ class Fp32LayerNorm(nn.LayerNorm):
37
+ def __init__(self, *args, **kwargs):
38
+ super().__init__(*args, **kwargs)
39
+
40
+ def forward(self, input):
41
+ output = F.layer_norm(
42
+ input.float(),
43
+ self.normalized_shape,
44
+ self.weight.float() if self.weight is not None else None,
45
+ self.bias.float() if self.bias is not None else None,
46
+ self.eps,
47
+ )
48
+ return output.type_as(input)
lscodec/models/fairseq_modules/transpose_last.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+ """
6
+ transpose last 2 dimensions of the input
7
+ """
8
+
9
+ import torch.nn as nn
10
+
11
+
12
+ class TransposeLast(nn.Module):
13
+ def __init__(self, deconstruct_idx=None, tranpose_dim=-2):
14
+ super().__init__()
15
+ self.deconstruct_idx = deconstruct_idx
16
+ self.tranpose_dim = tranpose_dim
17
+
18
+ def forward(self, x):
19
+ if self.deconstruct_idx is not None:
20
+ x = x[self.deconstruct_idx]
21
+ return x.transpose(self.tranpose_dim, -1)
lscodec/models/feature_extractor.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ # Portions of this code are a modification of the fairseq project.
8
+ # The original source code can be found at:
9
+ # https://github.com/facebookresearch/fairseq/blob/d13e14a800bb588e5a77fb4e551f554ff9b24a72/fairseq/models/wav2vec/wav2vec.py
10
+ #
11
+ # Copyright (c) 2025 Yiwei Guo
12
+ # Licensed under the MIT license.
13
+
14
+ import math
15
+
16
+ import torch.nn as nn
17
+ from lscodec.models.fairseq_modules.fp32_group_norm import Fp32GroupNorm
18
+ from lscodec.models.fairseq_modules.layer_norm import Fp32LayerNorm
19
+ from lscodec.models.fairseq_modules.transpose_last import TransposeLast
20
+ import torch
21
+
22
+
23
+ def norm_block(is_layer_norm, dim, affine=True):
24
+ if is_layer_norm:
25
+ mod = nn.Sequential(
26
+ TransposeLast(),
27
+ Fp32LayerNorm(dim, elementwise_affine=affine),
28
+ TransposeLast(),
29
+ )
30
+ else:
31
+ mod = Fp32GroupNorm(1, dim, affine=affine)
32
+
33
+ return mod
34
+
35
+
36
+ class ZeroPad1d(nn.Module):
37
+ def __init__(self, pad_left, pad_right):
38
+ super().__init__()
39
+ self.pad_left = pad_left
40
+ self.pad_right = pad_right
41
+
42
+ def forward(self, x):
43
+ return nn.functional.pad(x, (self.pad_left, self.pad_right))
44
+
45
+
46
+ class ConvFeatureExtractionModel(nn.Module):
47
+ def __init__(
48
+ self,
49
+ conv_layers,
50
+ dropout,
51
+ log_compression,
52
+ skip_connections,
53
+ residual_scale,
54
+ non_affine_group_norm,
55
+ activation,
56
+ ):
57
+ super().__init__()
58
+
59
+ def block(n_in, n_out, k, stride):
60
+ return nn.Sequential(
61
+ nn.Conv1d(n_in, n_out, k, stride=stride, bias=False),
62
+ nn.Dropout(p=dropout),
63
+ norm_block(
64
+ is_layer_norm=False, dim=n_out, affine=not non_affine_group_norm
65
+ ),
66
+ activation,
67
+ )
68
+
69
+ in_d = 1
70
+ self.conv_layers = nn.ModuleList()
71
+ for dim, k, stride in conv_layers:
72
+ self.conv_layers.append(block(in_d, dim, k, stride))
73
+ in_d = dim
74
+
75
+ self.log_compression = log_compression
76
+ self.skip_connections = skip_connections
77
+ self.residual_scale = math.sqrt(residual_scale)
78
+
79
+ def forward(self, x): # x is audio of shape [B, 1, T]
80
+ # Bx1xT -> BxCxT
81
+ for conv in self.conv_layers:
82
+ residual = x
83
+ x = conv(x)
84
+ if self.skip_connections and x.size(1) == residual.size(1):
85
+ tsz = x.size(2)
86
+ r_tsz = residual.size(2)
87
+ residual = residual[..., :: r_tsz // tsz][..., :tsz]
88
+ x = (x + residual) * self.residual_scale
89
+
90
+ if self.log_compression:
91
+ x = x.abs()
92
+ x = x + 1
93
+ x = x.log()
94
+
95
+ return x
96
+
97
+
98
+ class ConvAggregator(nn.Module):
99
+ def __init__(
100
+ self,
101
+ conv_layers,
102
+ embed,
103
+ dropout,
104
+ skip_connections,
105
+ residual_scale,
106
+ non_affine_group_norm,
107
+ conv_bias,
108
+ zero_pad,
109
+ activation,
110
+ ):
111
+ super().__init__()
112
+
113
+ def block(n_in, n_out, k, stride):
114
+ # padding dims only really make sense for stride = 1
115
+ ka = k // 2
116
+ kb = ka - 1 if k % 2 == 0 else ka
117
+
118
+ pad = (
119
+ ZeroPad1d(ka + kb, 0) if zero_pad else nn.ReplicationPad1d((ka + kb, 0))
120
+ )
121
+
122
+ return nn.Sequential(
123
+ pad,
124
+ nn.Conv1d(n_in, n_out, k, stride=stride, bias=conv_bias),
125
+ nn.Dropout(p=dropout),
126
+ norm_block(False, n_out, affine=not non_affine_group_norm),
127
+ activation,
128
+ )
129
+
130
+ in_d = embed
131
+ self.conv_layers = nn.ModuleList()
132
+ self.residual_proj = nn.ModuleList()
133
+ for dim, k, stride in conv_layers:
134
+ if in_d != dim and skip_connections:
135
+ self.residual_proj.append(nn.Conv1d(in_d, dim, 1, bias=False))
136
+ else:
137
+ self.residual_proj.append(None)
138
+
139
+ self.conv_layers.append(block(in_d, dim, k, stride))
140
+ in_d = dim
141
+ self.conv_layers = nn.Sequential(*self.conv_layers)
142
+ self.skip_connections = skip_connections
143
+ self.residual_scale = math.sqrt(residual_scale)
144
+
145
+ def forward(self, x):
146
+ for rproj, conv in zip(self.residual_proj, self.conv_layers):
147
+ residual = x
148
+ x = conv(x)
149
+ if self.skip_connections:
150
+ if rproj is not None:
151
+ residual = rproj(residual)
152
+ x = (x + residual) * self.residual_scale
153
+ return x
154
+
155
+
156
+ class LSCodecEncoder(nn.Module):
157
+ def __init__(self, conv_extraction_model: ConvFeatureExtractionModel,
158
+ conv_aggregation_model: ConvAggregator,
159
+ dropout_p: float = 0.1):
160
+ super().__init__()
161
+ self.conv_models = nn.Sequential(conv_extraction_model, conv_aggregation_model)
162
+
163
+ self.dropout = nn.Dropout(dropout_p)
164
+
165
+ def forward(self, waveform, frame_mask, length_tolerance=3):
166
+ # waveform: [B, 1, T]. Frame mask is 1 at valid values.
167
+ conv_features = self.conv_models(waveform) # [B, 1024, L]
168
+ conv_features = self.dropout(conv_features)
169
+ B, D, L = conv_features.shape
170
+ if frame_mask is None:
171
+ frame_mask = torch.ones(size=(B, L)).bool().to(conv_features.device)
172
+ if (frame_mask is not None) and (0 < frame_mask.shape[-1] - L <= length_tolerance):
173
+ pad_tensor = torch.zeros(size=(B, D, frame_mask.shape[-1] - L)).to(conv_features.device)
174
+ # NOTE: we don't pad these values with 999, but instead treat those as valid data points
175
+ # NOTE: this is because the mask is longer than the valid data points, and changing the mask is inconvenient.
176
+ conv_features = torch.concat([conv_features, pad_tensor], dim=-1)
177
+
178
+ return conv_features
lscodec/models/hifigan.py ADDED
@@ -0,0 +1,732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """HiFi-GAN Modules.
4
+
5
+ This code is based on https://github.com/jik876/hifi-gan.
6
+
7
+ """
8
+
9
+ import copy
10
+ import logging
11
+
12
+ import numpy as np
13
+ import torch
14
+ import torch.nn.functional as F
15
+
16
+ from lscodec.layers import HiFiGANResidualBlock as ResidualBlock
17
+ from lscodec.utils import read_hdf5
18
+
19
+
20
+ class HiFiGANGenerator(torch.nn.Module):
21
+ """HiFiGAN generator module."""
22
+
23
+ def __init__(
24
+ self,
25
+ in_channels=80,
26
+ out_channels=1,
27
+ channels=512,
28
+ kernel_size=7,
29
+ upsample_scales=(8, 8, 2, 2),
30
+ upsample_kernel_sizes=(16, 16, 4, 4),
31
+ resblock_kernel_sizes=(3, 7, 11),
32
+ resblock_dilations=[(1, 3, 5), (1, 3, 5), (1, 3, 5)],
33
+ use_additional_convs=True,
34
+ bias=True,
35
+ nonlinear_activation="LeakyReLU",
36
+ nonlinear_activation_params={"negative_slope": 0.1},
37
+ use_weight_norm=True,
38
+ ):
39
+ """Initialize HiFiGANGenerator module.
40
+
41
+ Args:
42
+ in_channels (int): Number of input channels.
43
+ out_channels (int): Number of output channels.
44
+ channels (int): Number of hidden representation channels.
45
+ kernel_size (int): Kernel size of initial and final conv layer.
46
+ upsample_scales (list): List of upsampling scales.
47
+ upsample_kernel_sizes (list): List of kernel sizes for upsampling layers.
48
+ resblock_kernel_sizes (list): List of kernel sizes for residual blocks.
49
+ resblock_dilations (list): List of dilation list for residual blocks.
50
+ use_additional_convs (bool): Whether to use additional conv layers in residual blocks.
51
+ bias (bool): Whether to add bias parameter in convolution layers.
52
+ nonlinear_activation (str): Activation function module name.
53
+ nonlinear_activation_params (dict): Hyperparameters for activation function.
54
+ use_weight_norm (bool): Whether to use weight norm.
55
+ If set to true, it will be applied to all the conv layers.
56
+
57
+ """
58
+ super().__init__()
59
+
60
+ # check hyperparameters are valid
61
+ assert kernel_size % 2 == 1, "Kernel size must be odd number."
62
+ assert len(upsample_scales) == len(upsample_kernel_sizes)
63
+ assert len(resblock_dilations) == len(resblock_kernel_sizes)
64
+
65
+ # define modules
66
+ self.num_upsamples = len(upsample_kernel_sizes)
67
+ self.num_blocks = len(resblock_kernel_sizes)
68
+ self.input_conv = torch.nn.Conv1d(
69
+ in_channels,
70
+ channels,
71
+ kernel_size,
72
+ 1,
73
+ padding=(kernel_size - 1) // 2,
74
+ )
75
+ self.upsamples = torch.nn.ModuleList()
76
+ self.blocks = torch.nn.ModuleList()
77
+ for i in range(len(upsample_kernel_sizes)):
78
+ assert upsample_kernel_sizes[i] == 2 * upsample_scales[i]
79
+ self.upsamples += [
80
+ torch.nn.Sequential(
81
+ getattr(torch.nn, nonlinear_activation)(
82
+ **nonlinear_activation_params
83
+ ),
84
+ torch.nn.ConvTranspose1d(
85
+ channels // (2 ** i),
86
+ channels // (2 ** (i + 1)),
87
+ upsample_kernel_sizes[i],
88
+ upsample_scales[i],
89
+ padding=upsample_scales[i] // 2 + upsample_scales[i] % 2,
90
+ output_padding=upsample_scales[i] % 2,
91
+ ),
92
+ )
93
+ ]
94
+ for j in range(len(resblock_kernel_sizes)):
95
+ self.blocks += [
96
+ ResidualBlock(
97
+ kernel_size=resblock_kernel_sizes[j],
98
+ channels=channels // (2 ** (i + 1)),
99
+ dilations=resblock_dilations[j],
100
+ bias=bias,
101
+ use_additional_convs=use_additional_convs,
102
+ nonlinear_activation=nonlinear_activation,
103
+ nonlinear_activation_params=nonlinear_activation_params,
104
+ )
105
+ ]
106
+ self.output_conv = torch.nn.Sequential(
107
+ # NOTE(kan-bayashi): follow official implementation but why
108
+ # using different slope parameter here? (0.1 vs. 0.01)
109
+ torch.nn.LeakyReLU(),
110
+ torch.nn.Conv1d(
111
+ channels // (2 ** (i + 1)),
112
+ out_channels,
113
+ kernel_size,
114
+ 1,
115
+ padding=(kernel_size - 1) // 2,
116
+ ),
117
+ torch.nn.Tanh(),
118
+ )
119
+
120
+ # apply weight norm
121
+ if use_weight_norm:
122
+ self.apply_weight_norm()
123
+
124
+ # reset parameters
125
+ self.reset_parameters()
126
+
127
+ def forward(self, c):
128
+ """Calculate forward propagation.
129
+
130
+ Args:
131
+ c (Tensor): Input tensor (B, in_channels, T).
132
+
133
+ Returns:
134
+ Tensor: Output tensor (B, out_channels, T).
135
+
136
+ """
137
+ c = self.input_conv(c)
138
+ for i in range(self.num_upsamples):
139
+ c = self.upsamples[i](c)
140
+ cs = 0.0 # initialize
141
+ for j in range(self.num_blocks):
142
+ cs += self.blocks[i * self.num_blocks + j](c)
143
+ c = cs / self.num_blocks
144
+ c = self.output_conv(c)
145
+
146
+ return c
147
+
148
+ def reset_parameters(self):
149
+ """Reset parameters.
150
+
151
+ This initialization follows the official implementation manner.
152
+ https://github.com/jik876/hifi-gan/blob/master/models.py
153
+
154
+ """
155
+
156
+ def _reset_parameters(m):
157
+ if isinstance(m, (torch.nn.Conv1d, torch.nn.ConvTranspose1d)):
158
+ m.weight.data.normal_(0.0, 0.01)
159
+ logging.debug(f"Reset parameters in {m}.")
160
+
161
+ self.apply(_reset_parameters)
162
+
163
+ def remove_weight_norm(self):
164
+ """Remove weight normalization module from all the layers."""
165
+
166
+ def _remove_weight_norm(m):
167
+ try:
168
+ logging.debug(f"Weight norm is removed from {m}.")
169
+ torch.nn.utils.remove_weight_norm(m)
170
+ except ValueError: # this module didn't have weight norm
171
+ return
172
+
173
+ self.apply(_remove_weight_norm)
174
+
175
+ def apply_weight_norm(self):
176
+ """Apply weight normalization module from all the layers."""
177
+
178
+ def _apply_weight_norm(m):
179
+ if isinstance(m, torch.nn.Conv1d) or isinstance(
180
+ m, torch.nn.ConvTranspose1d
181
+ ):
182
+ torch.nn.utils.weight_norm(m)
183
+ logging.debug(f"Weight norm is applied to {m}.")
184
+
185
+ self.apply(_apply_weight_norm)
186
+
187
+ def register_stats(self, stats):
188
+ """Register stats for de-normalization as buffer.
189
+
190
+ Args:
191
+ stats (str): Path of statistics file (".npy" or ".h5").
192
+
193
+ """
194
+ assert stats.endswith(".h5") or stats.endswith(".npy")
195
+ if stats.endswith(".h5"):
196
+ mean = read_hdf5(stats, "mean").reshape(-1)
197
+ scale = read_hdf5(stats, "scale").reshape(-1)
198
+ else:
199
+ mean = np.load(stats)[0].reshape(-1)
200
+ scale = np.load(stats)[1].reshape(-1)
201
+ self.register_buffer("mean", torch.from_numpy(mean).float())
202
+ self.register_buffer("scale", torch.from_numpy(scale).float())
203
+ logging.info("Successfully registered stats as buffer.")
204
+
205
+ def inference(self, c, normalize_before=False):
206
+ """Perform inference.
207
+
208
+ Args:
209
+ c (Union[Tensor, ndarray]): Input tensor (T, in_channels).
210
+ normalize_before (bool): Whether to perform normalization.
211
+
212
+ Returns:
213
+ Tensor: Output tensor (T ** prod(upsample_scales), out_channels).
214
+
215
+ """
216
+ # if not isinstance(c, torch.Tensor):
217
+ # c = torch.tensor(c, dtype=torch.float).to(next(self.parameters()).device)
218
+ if normalize_before:
219
+ c = (c - self.mean) / self.scale
220
+ c = self.forward(c.transpose(1, 2))
221
+ return c.squeeze(0).transpose(1, 0)
222
+
223
+
224
+ class HiFiGANPeriodDiscriminator(torch.nn.Module):
225
+ """HiFiGAN period discriminator module."""
226
+
227
+ def __init__(
228
+ self,
229
+ in_channels=1,
230
+ out_channels=1,
231
+ period=3,
232
+ kernel_sizes=[5, 3],
233
+ channels=32,
234
+ downsample_scales=[3, 3, 3, 3, 1],
235
+ max_downsample_channels=1024,
236
+ bias=True,
237
+ nonlinear_activation="LeakyReLU",
238
+ nonlinear_activation_params={"negative_slope": 0.1},
239
+ use_weight_norm=True,
240
+ use_spectral_norm=False,
241
+ ):
242
+ """Initialize HiFiGANPeriodDiscriminator module.
243
+
244
+ Args:
245
+ in_channels (int): Number of input channels.
246
+ out_channels (int): Number of output channels.
247
+ period (int): Period.
248
+ kernel_sizes (list): Kernel sizes of initial conv layers and the final conv layer.
249
+ channels (int): Number of initial channels.
250
+ downsample_scales (list): List of downsampling scales.
251
+ max_downsample_channels (int): Number of maximum downsampling channels.
252
+ use_additional_convs (bool): Whether to use additional conv layers in residual blocks.
253
+ bias (bool): Whether to add bias parameter in convolution layers.
254
+ nonlinear_activation (str): Activation function module name.
255
+ nonlinear_activation_params (dict): Hyperparameters for activation function.
256
+ use_weight_norm (bool): Whether to use weight norm.
257
+ If set to true, it will be applied to all the conv layers.
258
+ use_spectral_norm (bool): Whether to use spectral norm.
259
+ If set to true, it will be applied to all the conv layers.
260
+
261
+ """
262
+ super().__init__()
263
+ assert len(kernel_sizes) == 2
264
+ assert kernel_sizes[0] % 2 == 1, "Kernel size must be odd number."
265
+ assert kernel_sizes[1] % 2 == 1, "Kernel size must be odd number."
266
+
267
+ self.period = period
268
+ self.convs = torch.nn.ModuleList()
269
+ in_chs = in_channels
270
+ out_chs = channels
271
+ for downsample_scale in downsample_scales:
272
+ self.convs += [
273
+ torch.nn.Sequential(
274
+ torch.nn.Conv2d(
275
+ in_chs,
276
+ out_chs,
277
+ (kernel_sizes[0], 1),
278
+ (downsample_scale, 1),
279
+ padding=((kernel_sizes[0] - 1) // 2, 0),
280
+ ),
281
+ getattr(torch.nn, nonlinear_activation)(
282
+ **nonlinear_activation_params
283
+ ),
284
+ )
285
+ ]
286
+ in_chs = out_chs
287
+ # NOTE(kan-bayashi): Use downsample_scale + 1?
288
+ out_chs = min(out_chs * 4, max_downsample_channels)
289
+ self.output_conv = torch.nn.Conv2d(
290
+ out_chs,
291
+ out_channels,
292
+ (kernel_sizes[1] - 1, 1),
293
+ 1,
294
+ padding=((kernel_sizes[1] - 1) // 2, 0),
295
+ )
296
+
297
+ if use_weight_norm and use_spectral_norm:
298
+ raise ValueError("Either use use_weight_norm or use_spectral_norm.")
299
+
300
+ # apply weight norm
301
+ if use_weight_norm:
302
+ self.apply_weight_norm()
303
+
304
+ # apply spectral norm
305
+ if use_spectral_norm:
306
+ self.apply_spectral_norm()
307
+
308
+ def forward(self, x):
309
+ """Calculate forward propagation.
310
+
311
+ Args:
312
+ c (Tensor): Input tensor (B, in_channels, T).
313
+
314
+ Returns:
315
+ list: List of each layer's tensors.
316
+
317
+ """
318
+ # transform 1d to 2d -> (B, C, T/P, P)
319
+ b, c, t = x.shape
320
+ if t % self.period != 0:
321
+ n_pad = self.period - (t % self.period)
322
+ x = F.pad(x, (0, n_pad), "reflect")
323
+ t += n_pad
324
+ x = x.view(b, c, t // self.period, self.period)
325
+
326
+ # forward conv
327
+ outs = []
328
+ for layer in self.convs:
329
+ x = layer(x)
330
+ outs += [x]
331
+ x = self.output_conv(x)
332
+ x = torch.flatten(x, 1, -1)
333
+ outs += [x]
334
+
335
+ return outs
336
+
337
+ def apply_weight_norm(self):
338
+ """Apply weight normalization module from all the layers."""
339
+
340
+ def _apply_weight_norm(m):
341
+ if isinstance(m, torch.nn.Conv2d):
342
+ torch.nn.utils.weight_norm(m)
343
+ logging.debug(f"Weight norm is applied to {m}.")
344
+
345
+ self.apply(_apply_weight_norm)
346
+
347
+ def apply_spectral_norm(self):
348
+ """Apply spectral normalization module from all the layers."""
349
+
350
+ def _apply_spectral_norm(m):
351
+ if isinstance(m, torch.nn.Conv2d):
352
+ torch.nn.utils.spectral_norm(m)
353
+ logging.debug(f"Spectral norm is applied to {m}.")
354
+
355
+ self.apply(_apply_spectral_norm)
356
+
357
+
358
+ class HiFiGANMultiPeriodDiscriminator(torch.nn.Module):
359
+ """HiFiGAN multi-period discriminator module."""
360
+
361
+ def __init__(
362
+ self,
363
+ periods=[2, 3, 5, 7, 11],
364
+ discriminator_params={
365
+ "in_channels": 1,
366
+ "out_channels": 1,
367
+ "kernel_sizes": [5, 3],
368
+ "channels": 32,
369
+ "downsample_scales": [3, 3, 3, 3, 1],
370
+ "max_downsample_channels": 1024,
371
+ "bias": True,
372
+ "nonlinear_activation": "LeakyReLU",
373
+ "nonlinear_activation_params": {"negative_slope": 0.1},
374
+ "use_weight_norm": True,
375
+ "use_spectral_norm": False,
376
+ },
377
+ ):
378
+ """Initialize HiFiGANMultiPeriodDiscriminator module.
379
+
380
+ Args:
381
+ periods (list): List of periods.
382
+ discriminator_params (dict): Parameters for hifi-gan period discriminator module.
383
+ The period parameter will be overwritten.
384
+
385
+ """
386
+ super().__init__()
387
+ self.discriminators = torch.nn.ModuleList()
388
+ for period in periods:
389
+ params = copy.deepcopy(discriminator_params)
390
+ params["period"] = period
391
+ self.discriminators += [HiFiGANPeriodDiscriminator(**params)]
392
+
393
+ def forward(self, x):
394
+ """Calculate forward propagation.
395
+
396
+ Args:
397
+ x (Tensor): Input noise signal (B, 1, T).
398
+
399
+ Returns:
400
+ List: List of list of each discriminator outputs, which consists of each layer output tensors.
401
+
402
+ """
403
+ outs = []
404
+ for f in self.discriminators:
405
+ outs += [f(x)]
406
+
407
+ return outs
408
+
409
+
410
+ class HiFiGANScaleDiscriminator(torch.nn.Module):
411
+ """HiFi-GAN scale discriminator module."""
412
+
413
+ def __init__(
414
+ self,
415
+ in_channels=1,
416
+ out_channels=1,
417
+ kernel_sizes=[15, 41, 5, 3],
418
+ channels=128,
419
+ max_downsample_channels=1024,
420
+ max_groups=16,
421
+ bias=True,
422
+ downsample_scales=[2, 2, 4, 4, 1],
423
+ nonlinear_activation="LeakyReLU",
424
+ nonlinear_activation_params={"negative_slope": 0.1},
425
+ use_weight_norm=True,
426
+ use_spectral_norm=False,
427
+ ):
428
+ """Initilize HiFiGAN scale discriminator module.
429
+
430
+ Args:
431
+ in_channels (int): Number of input channels.
432
+ out_channels (int): Number of output channels.
433
+ kernel_sizes (list): List of four kernel sizes. The first will be used for the first conv layer,
434
+ and the second is for downsampling part, and the remaining two are for output layers.
435
+ channels (int): Initial number of channels for conv layer.
436
+ max_downsample_channels (int): Maximum number of channels for downsampling layers.
437
+ bias (bool): Whether to add bias parameter in convolution layers.
438
+ downsample_scales (list): List of downsampling scales.
439
+ nonlinear_activation (str): Activation function module name.
440
+ nonlinear_activation_params (dict): Hyperparameters for activation function.
441
+ use_weight_norm (bool): Whether to use weight norm.
442
+ If set to true, it will be applied to all the conv layers.
443
+ use_spectral_norm (bool): Whether to use spectral norm.
444
+ If set to true, it will be applied to all the conv layers.
445
+
446
+ """
447
+ super().__init__()
448
+ self.layers = torch.nn.ModuleList()
449
+
450
+ # check kernel size is valid
451
+ assert len(kernel_sizes) == 4
452
+ for ks in kernel_sizes:
453
+ assert ks % 2 == 1
454
+
455
+ # add first layer
456
+ self.layers += [
457
+ torch.nn.Sequential(
458
+ torch.nn.Conv1d(
459
+ in_channels,
460
+ channels,
461
+ # NOTE(kan-bayashi): Use always the same kernel size
462
+ kernel_sizes[0],
463
+ bias=bias,
464
+ padding=(kernel_sizes[0] - 1) // 2,
465
+ ),
466
+ getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
467
+ )
468
+ ]
469
+
470
+ # add downsample layers
471
+ in_chs = channels
472
+ out_chs = channels
473
+ # NOTE(kan-bayashi): Remove hard coding?
474
+ groups = 4
475
+ for downsample_scale in downsample_scales:
476
+ self.layers += [
477
+ torch.nn.Sequential(
478
+ torch.nn.Conv1d(
479
+ in_chs,
480
+ out_chs,
481
+ kernel_size=kernel_sizes[1],
482
+ stride=downsample_scale,
483
+ padding=(kernel_sizes[1] - 1) // 2,
484
+ groups=groups,
485
+ bias=bias,
486
+ ),
487
+ getattr(torch.nn, nonlinear_activation)(
488
+ **nonlinear_activation_params
489
+ ),
490
+ )
491
+ ]
492
+ in_chs = out_chs
493
+ # NOTE(kan-bayashi): Remove hard coding?
494
+ out_chs = min(in_chs * 2, max_downsample_channels)
495
+ # NOTE(kan-bayashi): Remove hard coding?
496
+ groups = min(groups * 4, max_groups)
497
+
498
+ # add final layers
499
+ out_chs = min(in_chs * 2, max_downsample_channels)
500
+ self.layers += [
501
+ torch.nn.Sequential(
502
+ torch.nn.Conv1d(
503
+ in_chs,
504
+ out_chs,
505
+ kernel_size=kernel_sizes[2],
506
+ stride=1,
507
+ padding=(kernel_sizes[2] - 1) // 2,
508
+ bias=bias,
509
+ ),
510
+ getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
511
+ )
512
+ ]
513
+ self.layers += [
514
+ torch.nn.Conv1d(
515
+ out_chs,
516
+ out_channels,
517
+ kernel_size=kernel_sizes[3],
518
+ stride=1,
519
+ padding=(kernel_sizes[3] - 1) // 2,
520
+ bias=bias,
521
+ ),
522
+ ]
523
+
524
+ if use_weight_norm and use_spectral_norm:
525
+ raise ValueError("Either use use_weight_norm or use_spectral_norm.")
526
+
527
+ # apply weight norm
528
+ if use_weight_norm:
529
+ self.apply_weight_norm()
530
+
531
+ # apply spectral norm
532
+ if use_spectral_norm:
533
+ self.apply_spectral_norm()
534
+
535
+ def forward(self, x):
536
+ """Calculate forward propagation.
537
+
538
+ Args:
539
+ x (Tensor): Input noise signal (B, 1, T).
540
+
541
+ Returns:
542
+ List: List of output tensors of each layer.
543
+
544
+ """
545
+ outs = []
546
+ for f in self.layers:
547
+ x = f(x)
548
+ outs += [x]
549
+
550
+ return outs
551
+
552
+ def apply_weight_norm(self):
553
+ """Apply weight normalization module from all the layers."""
554
+
555
+ def _apply_weight_norm(m):
556
+ if isinstance(m, torch.nn.Conv2d):
557
+ torch.nn.utils.weight_norm(m)
558
+ logging.debug(f"Weight norm is applied to {m}.")
559
+
560
+ self.apply(_apply_weight_norm)
561
+
562
+ def apply_spectral_norm(self):
563
+ """Apply spectral normalization module from all the layers."""
564
+
565
+ def _apply_spectral_norm(m):
566
+ if isinstance(m, torch.nn.Conv2d):
567
+ torch.nn.utils.spectral_norm(m)
568
+ logging.debug(f"Spectral norm is applied to {m}.")
569
+
570
+ self.apply(_apply_spectral_norm)
571
+
572
+
573
+ class HiFiGANMultiScaleDiscriminator(torch.nn.Module):
574
+ """HiFi-GAN multi-scale discriminator module."""
575
+
576
+ def __init__(
577
+ self,
578
+ scales=3,
579
+ downsample_pooling="AvgPool1d",
580
+ # follow the official implementation setting
581
+ downsample_pooling_params={
582
+ "kernel_size": 4,
583
+ "stride": 2,
584
+ "padding": 2,
585
+ },
586
+ discriminator_params={
587
+ "in_channels": 1,
588
+ "out_channels": 1,
589
+ "kernel_sizes": [15, 41, 5, 3],
590
+ "channels": 128,
591
+ "max_downsample_channels": 1024,
592
+ "max_groups": 16,
593
+ "bias": True,
594
+ "downsample_scales": [2, 2, 4, 4, 1],
595
+ "nonlinear_activation": "LeakyReLU",
596
+ "nonlinear_activation_params": {"negative_slope": 0.1},
597
+ },
598
+ follow_official_norm=False,
599
+ ):
600
+ """Initilize HiFiGAN multi-scale discriminator module.
601
+
602
+ Args:
603
+ scales (int): Number of multi-scales.
604
+ downsample_pooling (str): Pooling module name for downsampling of the inputs.
605
+ downsample_pooling_params (dict): Parameters for the above pooling module.
606
+ discriminator_params (dict): Parameters for hifi-gan scale discriminator module.
607
+ follow_official_norm (bool): Whether to follow the norm setting of the official
608
+ implementaion. The first discriminator uses spectral norm and the other
609
+ discriminators use weight norm.
610
+
611
+ """
612
+ super().__init__()
613
+ self.discriminators = torch.nn.ModuleList()
614
+
615
+ # add discriminators
616
+ for i in range(scales):
617
+ params = copy.deepcopy(discriminator_params)
618
+ if follow_official_norm:
619
+ if i == 0:
620
+ params["use_weight_norm"] = False
621
+ params["use_spectral_norm"] = True
622
+ else:
623
+ params["use_weight_norm"] = True
624
+ params["use_spectral_norm"] = False
625
+ self.discriminators += [HiFiGANScaleDiscriminator(**params)]
626
+ self.pooling = getattr(torch.nn, downsample_pooling)(
627
+ **downsample_pooling_params
628
+ )
629
+
630
+ def forward(self, x):
631
+ """Calculate forward propagation.
632
+
633
+ Args:
634
+ x (Tensor): Input noise signal (B, 1, T).
635
+
636
+ Returns:
637
+ List: List of list of each discriminator outputs, which consists of each layer output tensors.
638
+
639
+ """
640
+ outs = []
641
+ for f in self.discriminators:
642
+ outs += [f(x)]
643
+ x = self.pooling(x)
644
+
645
+ return outs
646
+
647
+
648
+ class HiFiGANMultiScaleMultiPeriodDiscriminator(torch.nn.Module):
649
+ """HiFi-GAN multi-scale + multi-period discriminator module."""
650
+
651
+ def __init__(
652
+ self,
653
+ # Multi-scale discriminator related
654
+ scales=3,
655
+ scale_downsample_pooling="AvgPool1d",
656
+ scale_downsample_pooling_params={
657
+ "kernel_size": 4,
658
+ "stride": 2,
659
+ "padding": 2,
660
+ },
661
+ scale_discriminator_params={
662
+ "in_channels": 1,
663
+ "out_channels": 1,
664
+ "kernel_sizes": [15, 41, 5, 3],
665
+ "channels": 128,
666
+ "max_downsample_channels": 1024,
667
+ "max_groups": 16,
668
+ "bias": True,
669
+ "downsample_scales": [2, 2, 4, 4, 1],
670
+ "nonlinear_activation": "LeakyReLU",
671
+ "nonlinear_activation_params": {"negative_slope": 0.1},
672
+ },
673
+ follow_official_norm=True,
674
+ # Multi-period discriminator related
675
+ periods=[2, 3, 5, 7, 11],
676
+ period_discriminator_params={
677
+ "in_channels": 1,
678
+ "out_channels": 1,
679
+ "kernel_sizes": [5, 3],
680
+ "channels": 32,
681
+ "downsample_scales": [3, 3, 3, 3, 1],
682
+ "max_downsample_channels": 1024,
683
+ "bias": True,
684
+ "nonlinear_activation": "LeakyReLU",
685
+ "nonlinear_activation_params": {"negative_slope": 0.1},
686
+ "use_weight_norm": True,
687
+ "use_spectral_norm": False,
688
+ },
689
+ ):
690
+ """Initilize HiFiGAN multiscale + multi-period discriminator module.
691
+
692
+ Args:
693
+ scales (int): Number of multi-scales.
694
+ scale_downsample_pooling (str): Pooling module name for downsampling of the inputs.
695
+ scale_downsample_pooling_params (dict): Parameters for the above pooling module.
696
+ scale_discriminator_params (dict): Parameters for hifi-gan scale discriminator module.
697
+ follow_official_norm (bool): Whether to follow the norm setting of the official
698
+ implementaion. The first discriminator uses spectral norm and the other
699
+ discriminators use weight norm.
700
+ periods (list): List of periods.
701
+ period_discriminator_params (dict): Parameters for hifi-gan period discriminator module.
702
+ The period parameter will be overwritten.
703
+
704
+ """
705
+ super().__init__()
706
+ self.msd = HiFiGANMultiScaleDiscriminator(
707
+ scales=scales,
708
+ downsample_pooling=scale_downsample_pooling,
709
+ downsample_pooling_params=scale_downsample_pooling_params,
710
+ discriminator_params=scale_discriminator_params,
711
+ follow_official_norm=follow_official_norm,
712
+ )
713
+ self.mpd = HiFiGANMultiPeriodDiscriminator(
714
+ periods=periods,
715
+ discriminator_params=period_discriminator_params,
716
+ )
717
+
718
+ def forward(self, x):
719
+ """Calculate forward propagation.
720
+
721
+ Args:
722
+ x (Tensor): Input noise signal (B, 1, T).
723
+
724
+ Returns:
725
+ List: List of list of each discriminator outputs,
726
+ which consists of each layer output tensors.
727
+ Multiscale and multi period ones are concatenated.
728
+
729
+ """
730
+ msd_outs = self.msd(x)
731
+ mpd_outs = self.mpd(x)
732
+ return msd_outs + mpd_outs