Skip to content

Commit 0839ead

Browse files
committed
more
1 parent 367cf22 commit 0839ead

3 files changed

Lines changed: 282 additions & 16 deletions

File tree

users/zeyer/experiments/exp2024_04_23_baselines/aed.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,7 @@ def encode_no_transform(
11401140
in_spatial_dim: Dim,
11411141
collected_outputs: Optional[Dict[str, Tensor]] = None,
11421142
specaugment_max_spatial_dims: Optional[Tensor] = None,
1143+
end_layer: Optional[int] = None,
11431144
) -> Tuple[Tensor, Dim]:
11441145
"""encode, and extend the encoder output for things we need in the decoder"""
11451146
if self.pad_audio:
@@ -1151,6 +1152,7 @@ def encode_no_transform(
11511152
in_spatial_dim=in_spatial_dim,
11521153
collected_outputs=collected_outputs,
11531154
specaugment_max_spatial_dims=specaugment_max_spatial_dims,
1155+
end_layer=end_layer,
11541156
)
11551157

11561158
def encode_from_features(
@@ -1160,11 +1162,15 @@ def encode_from_features(
11601162
in_spatial_dim: Dim,
11611163
collected_outputs: Optional[Dict[str, Tensor]] = None,
11621164
specaugment_max_spatial_dims: Optional[Tensor] = None,
1165+
end_layer: Optional[int] = None,
11631166
) -> Tuple[Tensor, Dim]:
11641167
"""Encode from already-extracted features (e.g. log-mel produced online by a TTS model),
11651168
skipping pad_audio + feature_extraction. source feature dim must be self.in_dim.
11661169
specaugment_max_spatial_dims (per-seq) overrides the SpecAugment time-mask width,
1167-
e.g. scaled down for short synthetic sequences."""
1170+
e.g. scaled down for short synthetic sequences.
1171+
end_layer: if set, stop after Conformer layers [0, end_layer)
1172+
(the audio-side counterpart of :func:`encode_from_enc_space`;
1173+
output is in the encoder model space at the encoder frame rate, NOT decoder-transformed)."""
11681174
if self.feature_batch_norm:
11691175
if self.feature_norm_wants_spatial_dim:
11701176
source = self.feature_batch_norm(source, spatial_dim=in_spatial_dim)
@@ -1186,9 +1192,27 @@ def encode_from_features(
11861192
feature_dim=self.in_dim,
11871193
**specaugment_opts,
11881194
)
1189-
# Encoder including convolutional frontend
1190-
enc, enc_spatial_dim = self.encoder(source, in_spatial_dim=in_spatial_dim, collected_outputs=collected_outputs)
1191-
return enc, enc_spatial_dim
1195+
if end_layer is None: # standard case
1196+
# Encoder including convolutional frontend
1197+
enc, enc_spatial_dim = self.encoder(
1198+
source, in_spatial_dim=in_spatial_dim, collected_outputs=collected_outputs
1199+
)
1200+
return enc, enc_spatial_dim
1201+
# Partial encoder: conv frontend + Conformer layers [0, end_layer)
1202+
# (mirrors ConformerEncoder.__call__; same assumptions as encode_from_enc_space).
1203+
x, enc_spatial_dim = self.encoder.input_layer(source, in_spatial_dim=in_spatial_dim)
1204+
if self.encoder.input_projection and self.encoder.input_projection.in_dim in x.dims:
1205+
x = self.encoder.input_projection(x)
1206+
assert self.encoder.out_dim in x.dims
1207+
assert self.encoder.pos_enc is None and self.encoder.input_embedding_scale == 1.0
1208+
x = rf.dropout(x, self.encoder.input_dropout, axis=self.encoder.dropout_broadcast and self.encoder.out_dim)
1209+
for name, layer in self.encoder.layers.items():
1210+
if int(name) >= end_layer:
1211+
break
1212+
x = layer(x, spatial_dim=enc_spatial_dim)
1213+
if collected_outputs is not None:
1214+
collected_outputs[name] = x
1215+
return x, enc_spatial_dim
11921216

11931217
def encode_from_enc_space(
11941218
self,
@@ -1198,6 +1222,8 @@ def encode_from_enc_space(
11981222
start_layer: int = 0,
11991223
collected_outputs: Optional[Dict[str, Tensor]] = None,
12001224
specaugment_max_spatial_dims: Optional[Tensor] = None,
1225+
apply_specaugment: bool = True,
1226+
apply_input_dropout: bool = True,
12011227
) -> Tuple[Tensor, Dim]:
12021228
"""Encode from features already in the encoder model space
12031229
(feature dim = encoder out_dim, at the subsampled encoder frame rate),
@@ -1208,19 +1234,22 @@ def encode_from_enc_space(
12081234
``specaugment_max_spatial_dims``: as in :func:`encode_from_features`,
12091235
but the time-mask width counts encoder frames here."""
12101236
assert self.encoder.out_dim in source.dims
1211-
specaugment_opts = self._specaugment_opts
1212-
if specaugment_max_spatial_dims is not None:
1213-
specaugment_opts = {**specaugment_opts, "max_consecutive_spatial_dims": specaugment_max_spatial_dims}
1214-
source = rf.audio.specaugment(
1215-
source,
1216-
spatial_dim=spatial_dim,
1217-
feature_dim=self.encoder.out_dim,
1218-
**specaugment_opts,
1219-
)
1237+
if apply_specaugment:
1238+
specaugment_opts = self._specaugment_opts
1239+
if specaugment_max_spatial_dims is not None:
1240+
specaugment_opts = {**specaugment_opts, "max_consecutive_spatial_dims": specaugment_max_spatial_dims}
1241+
source = rf.audio.specaugment(
1242+
source,
1243+
spatial_dim=spatial_dim,
1244+
feature_dim=self.encoder.out_dim,
1245+
**specaugment_opts,
1246+
)
12201247
# Absolute pos enc / input scaling would belong before the first layer; not handled here
12211248
# (this baseline uses rel pos enc inside the layers).
12221249
assert self.encoder.pos_enc is None and self.encoder.input_embedding_scale == 1.0
1223-
x = rf.dropout(source, self.encoder.input_dropout, axis=self.encoder.dropout_broadcast and self.encoder.out_dim)
1250+
x = source
1251+
if apply_input_dropout:
1252+
x = rf.dropout(x, self.encoder.input_dropout, axis=self.encoder.dropout_broadcast and self.encoder.out_dim)
12241253
for name, layer in self.encoder.layers.items():
12251254
if int(name) < start_layer:
12261255
continue

users/zeyer/experiments/exp2026_05_28_tts_encoder_fzj.py

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,7 +1207,9 @@ def _train_tts_encoder(
12071207
from i6_experiments.users.zeyer.recog import recog_training_exp as recog_training_func
12081208

12091209
if single_stream:
1210-
assert tts_waveform and not pseudo_speech_enc, "single_stream: waveform TTS path only"
1210+
assert tts_waveform or (pseudo_speech_enc and pseudo_enc_start_layer >= 0), (
1211+
"single_stream: waveform TTS, or layer-split pseudo-enc"
1212+
)
12111213

12121214
vocab = "spm10k"
12131215
task = get_librispeech_task_raw_v2(
@@ -1398,7 +1400,11 @@ def _train_tts_encoder(
13981400
# (offline-reference style); otherwise alternate_batching (pure audio / pure text batches).
13991401
**({} if single_stream else {"torch_batching": functools.partial(alternate_batching, asr_key=in_key)}),
14001402
# custom step; no TrainDef needed
1401-
"train_step": aed_glowtts_single_stream_train_step if single_stream else aed_glowtts_train_step,
1403+
"train_step": (
1404+
(aed_pseudo_enc_single_stream_train_step if pseudo_speech_enc else aed_glowtts_single_stream_train_step)
1405+
if single_stream
1406+
else aed_glowtts_train_step
1407+
),
14021408
"learning_rate_control_error_measure": "ce", # set explicitly since there is no TrainDef
14031409
"speed_pert_discrete_values": [0.7, 0.8, 0.9, 1.0, 1.1],
14041410
"aux_loss_layers": [4, 10, 16],
@@ -2089,6 +2095,213 @@ def aed_glowtts_single_stream_train_step(*, model: Model, extern_data, **_kwargs
20892095
frame_error.mark_as_loss(name=f"fer{postfix}", as_error=True)
20902096

20912097

2098+
def aed_pseudo_enc_single_stream_train_step(*, model: Model, extern_data, **_kwargs_unused):
2099+
"""Custom RETURNN train_step: single-stream AED+CTC with the pseudo-speech-encoder
2100+
(real audio + pseudo-enc text seqs mixed in one batch).
2101+
2102+
Like aed_glowtts_single_stream_train_step, but the text branch enters the encoder
2103+
at layer pseudo_enc_start_layer (encoder model space, enc frame rate) instead of at the waveform:
2104+
the pseudo features are computed on the FULL batch
2105+
(audio rows have empty phoneme seqs -> empty rows; a plain embedding lookup, so this is free)
2106+
and get the text-side SpecAugment + input dropout;
2107+
the audio rows run the front-end + Conformer layers [0, start_layer)
2108+
and are scattered in at the layer boundary (rf.nested handles the ragged frame dims);
2109+
Conformer layers [start_layer, ...) + decoder + ONE loss set run once on the merged batch.
2110+
Aux CTC tapped below start_layer (ctc_4 at start_layer=4) is computed on the audio rows only
2111+
(same coverage as dual-stream, where the text branch skips those aux losses).
2112+
"""
2113+
from returnn.config import get_global_config
2114+
from returnn.util.collect_outputs_dict import CollectOutputsDict
2115+
2116+
config = get_global_config() # noqa
2117+
assert config.bool("pseudo_speech_enc", False)
2118+
start_layer = config.int("pseudo_enc_start_layer", -1)
2119+
assert start_layer >= 0, "pseudo-enc single-stream: layer-split injection only"
2120+
assert config.typed_value("pseudo_enc_units", "phonemes") == "phonemes", "pseudo-enc single-stream: phonemes only"
2121+
data = extern_data[config.typed_value("default_input")]
2122+
data_spatial_dim = data.get_time_dim_tag()
2123+
targets = extern_data[config.typed_value("target")]
2124+
targets_spatial_dim = targets.get_time_dim_tag()
2125+
phonemes = extern_data[PHONEMES_DATA_KEY]
2126+
phonemes_spatial_dim = phonemes.get_time_dim_tag()
2127+
2128+
aux_loss_layers = config.typed_value("aux_loss_layers") or ()
2129+
aux_loss_scales = config.typed_value("aux_loss_scales", [1.0] * len(aux_loss_layers))
2130+
aed_loss_scale = config.float("aed_loss_scale", 1.0)
2131+
dec_aux_loss_layers = config.typed_value("dec_aux_loss_layers") or ()
2132+
dec_aux_loss_scales = config.typed_value("dec_aux_loss_scales", [1.0] * len(dec_aux_loss_layers))
2133+
use_normalized_loss = config.typed_value("use_normalized_loss", True)
2134+
if isinstance(use_normalized_loss, bool):
2135+
use_normalized_loss = "frames" if use_normalized_loss else "none"
2136+
assert use_normalized_loss in ("none", "frames"), f"single_stream: unsupported {use_normalized_loss!r}"
2137+
normed = {"none": False, "frames": True}[use_normalized_loss]
2138+
label_smoothing = config.float("label_smoothing", 0.1)
2139+
pseudo_specaug_width = config.typed_value("pseudo_enc_specaug_max_width", None)
2140+
2141+
if data.feature_dim and data.feature_dim.dimension == 1:
2142+
data = rf.squeeze(data, axis=data.feature_dim)
2143+
(batch_dim,) = data.remaining_dims(data_spatial_dim)
2144+
# per-seq split: text seqs carry empty audio (size 0); real-audio seqs carry empty phonemes.
2145+
text_mask_cpu = data_spatial_dim.get_size_tensor() <= 0
2146+
audio_mask_cpu = rf.logical_not(text_mask_cpu)
2147+
audio_mask = rf.copy_to_device(audio_mask_cpu, data.device)
2148+
num_text = int(text_mask_cpu.raw_tensor.sum())
2149+
num_total = int(batch_dim.get_dim_value())
2150+
2151+
if config.bool("use_eos_postfix", False):
2152+
ctc_targets, (ctc_targets_spatial_dim,) = rf.pad(
2153+
targets, axes=[targets_spatial_dim], padding=[(0, 1)], value=model.eos_idx
2154+
)
2155+
else:
2156+
ctc_targets, ctc_targets_spatial_dim = targets, targets_spatial_dim
2157+
2158+
n_layers = len(model.encoder.layers)
2159+
# aux heads tapped below the merge boundary (collected key < start_layer): audio rows only.
2160+
aux_below = [i for i in aux_loss_layers if i - 1 < start_layer]
2161+
collected = CollectOutputsDict(allowed_key_patterns=[str(i - 1) for i in aux_loss_layers])
2162+
aux_below_out = {}
2163+
audio_ct = audio_ct_sp = aux_below_sp = None
2164+
if num_text == 0:
2165+
# pure real-audio batch: the standard full encode (all aux keys collected).
2166+
enc, enc_spatial_dim = model.encode(data, in_spatial_dim=data_spatial_dim, collected_outputs=collected)
2167+
elif num_text == num_total:
2168+
# pure text batch: the plain layer-split path (aux below start_layer skipped, as in dual-stream).
2169+
feats, feats_spatial_dim = model.pseudo_enc(phonemes, spatial_dim=phonemes_spatial_dim)
2170+
enc_raw, enc_spatial_dim = model.encode_from_enc_space(
2171+
feats,
2172+
spatial_dim=feats_spatial_dim,
2173+
start_layer=start_layer,
2174+
collected_outputs=collected,
2175+
specaugment_max_spatial_dims=pseudo_specaug_width,
2176+
)
2177+
enc = model.decoder.transform_encoder(enc_raw, axis=enc_spatial_dim)
2178+
else:
2179+
# text side: pseudo features on the FULL batch (audio rows are empty), then the text-side
2180+
# SpecAugment (scaled width) + input dropout; start_layer=n_layers runs no Conformer layer.
2181+
feats_t, feats_t_spatial_dim = model.pseudo_enc(phonemes, spatial_dim=phonemes_spatial_dim)
2182+
feats_t, _ = model.encode_from_enc_space(
2183+
feats_t,
2184+
spatial_dim=feats_t_spatial_dim,
2185+
start_layer=n_layers,
2186+
specaugment_max_spatial_dims=pseudo_specaug_width,
2187+
)
2188+
# audio side: select the real-audio rows, front-end + Conformer layers [0, start_layer).
2189+
(wave_a, wave_a_spatial_dim), audio_bdim, sel_map_a = rf.nested.masked_select_nested(
2190+
(data, data_spatial_dim), mask=audio_mask, mask_cpu=audio_mask_cpu, dims=[batch_dim]
2191+
)
2192+
coll_a = CollectOutputsDict(allowed_key_patterns=[str(i - 1) for i in aux_below])
2193+
feats_a, feats_a_spatial_dim = model.encode_no_transform(
2194+
wave_a, in_spatial_dim=wave_a_spatial_dim, collected_outputs=coll_a, end_layer=start_layer
2195+
)
2196+
if aux_below:
2197+
(audio_ct, audio_ct_sp), _, _ = rf.nested.masked_select_nested(
2198+
(ctc_targets, ctc_targets_spatial_dim), mask=audio_mask, mask_cpu=audio_mask_cpu, dims=[batch_dim]
2199+
)
2200+
aux_below_out = {i: coll_a[str(i - 1)] for i in aux_below}
2201+
aux_below_sp = feats_a_spatial_dim
2202+
# merge at the layer boundary: scatter the audio rows into the full-batch pseudo features.
2203+
if feats_t.dtype != feats_a.dtype:
2204+
feats_t = rf.cast(feats_t, feats_a.dtype)
2205+
x, x_spatial_dim = rf.nested.masked_scatter_nested(
2206+
(feats_a, feats_a_spatial_dim),
2207+
(feats_t, feats_t_spatial_dim),
2208+
mask=audio_mask,
2209+
mask_cpu=audio_mask_cpu,
2210+
dims=[batch_dim],
2211+
in_dim=audio_bdim,
2212+
masked_select_dim_map=sel_map_a,
2213+
)
2214+
enc_raw, enc_spatial_dim = model.encode_from_enc_space(
2215+
x,
2216+
spatial_dim=x_spatial_dim,
2217+
start_layer=start_layer,
2218+
collected_outputs=collected,
2219+
apply_specaugment=False,
2220+
apply_input_dropout=False,
2221+
)
2222+
enc = model.decoder.transform_encoder(enc_raw, axis=enc_spatial_dim)
2223+
2224+
for i, layer_idx in enumerate(aux_loss_layers):
2225+
if layer_idx > n_layers:
2226+
continue
2227+
key = str(layer_idx - 1)
2228+
if key in collected:
2229+
aux_out, aux_sp = collected[key], enc_spatial_dim
2230+
aux_tgt, aux_tgt_sp = ctc_targets, ctc_targets_spatial_dim
2231+
elif layer_idx in aux_below_out:
2232+
# tapped below the merge: audio rows only (dual-stream text skips these too).
2233+
aux_out, aux_sp = aux_below_out[layer_idx], aux_below_sp
2234+
aux_tgt, aux_tgt_sp = audio_ct, audio_ct_sp
2235+
else:
2236+
continue # pure-text batch: no signal below the injection layer
2237+
aux_logits = getattr(model, f"enc_aux_logits_{layer_idx}")(aux_out)
2238+
aux_ctc_log_probs = rf.log_softmax(aux_logits, axis=model.wb_target_dim)
2239+
aux_loss = rf.ctc_loss(
2240+
logits=aux_ctc_log_probs,
2241+
logits_normalized=True,
2242+
targets=aux_tgt,
2243+
input_spatial_dim=aux_sp,
2244+
targets_spatial_dim=aux_tgt_sp,
2245+
blank_index=model.blank_idx,
2246+
)
2247+
aux_loss.mark_as_loss(
2248+
f"ctc_{layer_idx}",
2249+
scale=aux_loss_scales[i],
2250+
custom_inv_norm_factor=aux_tgt_sp.get_size_tensor(),
2251+
use_normalized_loss=normed,
2252+
)
2253+
2254+
# AED CE + FER (single stream), identical to aed_glowtts_single_stream_train_step.
2255+
batch_dims = targets.remaining_dims(targets_spatial_dim)
2256+
input_labels, (targets_w_eos_spatial_dim,) = rf.pad(
2257+
targets, axes=[targets_spatial_dim], padding=[(1, 0)], value=model.bos_idx
2258+
)
2259+
targets_w_eos, _ = rf.pad(
2260+
targets,
2261+
axes=[targets_spatial_dim],
2262+
padding=[(0, 1)],
2263+
value=model.eos_idx,
2264+
out_dims=[targets_w_eos_spatial_dim],
2265+
)
2266+
dec_collected = CollectOutputsDict(allowed_key_patterns=[str(i - 1) for i in dec_aux_loss_layers])
2267+
logits, _ = model.decoder(
2268+
input_labels,
2269+
spatial_dim=targets_w_eos_spatial_dim,
2270+
encoder=enc,
2271+
state=model.decoder.default_initial_state(batch_dims=batch_dims),
2272+
collected_outputs=dec_collected,
2273+
)
2274+
dec_aux_logits = {}
2275+
for layer_idx in dec_aux_loss_layers:
2276+
norm = getattr(model, f"dec_aux_final_layer_norm_{layer_idx}")
2277+
linear = getattr(model, f"dec_aux_logits_{layer_idx}")
2278+
dec_aux_logits[layer_idx] = linear(norm(dec_collected[str(layer_idx - 1)]))
2279+
2280+
targets_packed, pack_dim = rf.pack_padded(
2281+
targets_w_eos, dims=batch_dims + [targets_w_eos_spatial_dim], enforce_sorted=False
2282+
)
2283+
for postfix, scale, logits_ in [("", aed_loss_scale, logits)] + [
2284+
(f"_{k}", dec_aux_loss_scales[i], dec_aux_logits[k]) for i, k in enumerate(dec_aux_loss_layers)
2285+
]:
2286+
logits_packed, _ = rf.pack_padded(
2287+
logits_, dims=batch_dims + [targets_w_eos_spatial_dim], enforce_sorted=False, out_dim=pack_dim
2288+
)
2289+
if not model.out_eos_separated:
2290+
log_prob = rf.log_softmax(logits_packed, axis=model.target_dim)
2291+
else:
2292+
log_prob = _aed.log_probs_with_eos_separated(
2293+
logits_packed, target_dim=model.target_dim, eos_idx=model.eos_idx
2294+
)
2295+
log_prob = rf.label_smoothed_log_prob_gradient(log_prob, label_smoothing, axis=model.target_dim)
2296+
loss = rf.cross_entropy(
2297+
target=targets_packed, estimated=log_prob, estimated_type="log-probs", axis=model.target_dim
2298+
)
2299+
loss.mark_as_loss(f"ce{postfix}", scale=scale, use_normalized_loss=normed)
2300+
best = rf.reduce_argmax(log_prob, axis=model.target_dim)
2301+
frame_error = best != targets_packed
2302+
frame_error.mark_as_loss(name=f"fer{postfix}", as_error=True)
2303+
2304+
20922305
_glowtts_text_tok_cache = None
20932306

20942307

users/zeyer/experiments/exp2026_05_28_tts_encoder_rz.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,30 @@ def py():
340340
gpu_mem=96,
341341
nep=100,
342342
)
343+
# Single-stream version of pseudo-enc-layer4-noblank-1gpu:
344+
# audio + text mixed in ONE batch (no alternate batching), merged at the layer-4 boundary,
345+
# one loss set -- the pseudo-enc analogue of tts-enc-logmel-refcfg-single
346+
# (same mixing-structure question, on the cheapest injection mechanism).
347+
_train_tts_encoder(
348+
"pseudo-enc-layer4-noblank-single-1gpu",
349+
prefix=prefix,
350+
text_train_epoch_split=75,
351+
# 70k/6k, not the family's 120k/8k: everything runs on c25g (80GB) now,
352+
# and merged batches stack audio AND text in one step
353+
# (100k/8k OOM'd at step 0: 69GB + 13.7GB backward).
354+
batch_size_audio_frames=70_000,
355+
max_phon_len=300,
356+
batch_size_phon=6_000,
357+
asr_logmel=True,
358+
pseudo_speech_enc=True,
359+
pseudo_enc_start_layer=4,
360+
pseudo_enc_blank_duration_range=(0, 0),
361+
pseudo_enc_specaug_max_width=6,
362+
single_stream=True,
363+
num_processes=1,
364+
gpu_mem=96,
365+
nep=100,
366+
)
343367
# Muon version of pseudo-enc-layer4-1gpu (muon-lr5e3-wdbl):
344368
# best FZJ mechanism + best optimizer in the single-GPU nep100 regime,
345369
# the best-number chase, vs the AdamW #1 which is the fair 3.53 comparison.

0 commit comments

Comments
 (0)