🐛 Describe the bug
torchaudio.models.Conformer gives different outputs for the same sequence depending on how many padding frames are appended to it (even in eval() mode with correct lengths). (In practice the padding amount varies because sequences are padded to the longest one in their batch, so a sequence's output silently depends on its batch mates.) Attention respects the padding mask derived from lengths, but _ConvolutionModule ignores it, in two ways:
- Padding bleeds into valid frames through the depthwise convolution. The depthwise conv reads each frame's neighbors, so a sequence's last valid frames pull in whatever follows them. Run alone, that's the conv's own zero-padding; batched with a longer sequence, it's the padding frames - which carry non-zeros (even if they start at zero). So non-zero padding leaks into valid frames, and attention then carries it across the whole sequence.
- Normalization statistics include padded steps. Both
BatchNorm1d (default) and GroupNorm (use_group_norm=True) compute their mean/var over the time axis, so padded frames enter the statistics and skew the normalization applied to the valid ones.
Minimal reproduction:
import torch
from torchaudio.models import Conformer
torch.manual_seed(0)
INPUT_DIM, SHORT_LEN, LONG_LEN = 80, 50, 400
model = Conformer(
input_dim=INPUT_DIM, num_heads=4, ffn_dim=128, num_layers=4, depthwise_conv_kernel_size=31
).eval()
short = torch.rand(1, SHORT_LEN, INPUT_DIM)
long = torch.rand(1, LONG_LEN, INPUT_DIM)
# (a) the short sequence alone in its batch: no padding at all
out_alone, _ = model(short, torch.tensor([SHORT_LEN]))
# (b) the same sequence zero-padded to share a batch with the long sequence
padded_short = torch.nn.functional.pad(short, (0, 0, 0, LONG_LEN - SHORT_LEN))
out_batch, _ = model(torch.cat([long, padded_short]), torch.tensor([LONG_LEN, SHORT_LEN]))
max_diff = (out_alone[0] - out_batch[1, :SHORT_LEN]).abs().max()
scale = out_alone.abs().mean()
print(f"max |out_alone - out_batched| = {max_diff:.6f} (mean |out| = {scale:.4f})")
Observed:
max |out_alone - out_batched| = 0.282953 (mean |out| = 0.8238)
Expected: a difference at float-precision level (~1e-6), since both calls process the same sequence and lengths marks the padding. With use_group_norm=True the same script gives max diff = 1.226370 — larger than the mean output magnitude.
Real-world impact. I hit this in practice (proshian/neural-swipe-typing, swipe-gesture decoding, where sequence lengths vary a lot within a batch). A Conformer encoder trained with large mixed-length batches had strong batched-validation metrics, but when switched to deployment-style inference (one sequence at a time, no padding) the metrics dropped dramatically. The model had learned to rely on padding-dependent behavior; nothing in the API suggests that the output of Conformer(input, lengths) depends on batch composition.
How other implementations handle this. Masking before the depthwise convolution and offering a padding-invariant normalization:
I have a backward-compatible fix (opt-in conv_mask_padding and conv_norm_type arguments; default behavior and state_dict layout unchanged) and will open a PR referencing this issue.
Versions
Collecting environment information...
PyTorch version: 2.13.0+cpu
Is debug build: False
CUDA used to build PyTorch: None
ROCM used to build PyTorch: N/A
OS: Microsoft Windows 10 Pro (10.0.19045 64-bit)
GCC version: (rubenvb-4.6.3) 4.6.3
Clang version: 17.0.1
CMake version: version 4.2.1
Libc version: N/A
Python version: 3.12.9 (main, Feb 12 2025, 14:52:31) [MSC v.1942 64 bit (AMD64)] (64-bit runtime)
Python platform: Windows-10-10.0.19045-SP0
Is CUDA available: False
CUDA runtime version: No CUDA
CUDA_MODULE_LOADING set to: N/A
GPU models and configuration: No CUDA
Nvidia driver version: No CUDA
cuDNN version: No CUDA
Is XPU available: False
HIP runtime version: N/A
MIOpen runtime version: N/A
Is XNNPACK available: False
Caching allocator config: N/A
CPU:
Name: Intel(R) Core(TM) i3-6100U CPU @ 2.30GHz
Manufacturer: GenuineIntel
Family: 206
Architecture: 9
ProcessorType: 3
DeviceID: CPU0
CurrentClockSpeed: 2301
MaxClockSpeed: 2301
L2CacheSize: 512
L2CacheSpeed: None
Revision: 19971
Versions of relevant libraries:
[pip3] numpy==2.5.1
[pip3] torch==2.13.0+cpu
[pip3] torchaudio==2.11.0+cpu
[conda] No relevant packages
🐛 Describe the bug
torchaudio.models.Conformergives different outputs for the same sequence depending on how many padding frames are appended to it (even ineval()mode with correctlengths). (In practice the padding amount varies because sequences are padded to the longest one in their batch, so a sequence's output silently depends on its batch mates.) Attention respects the padding mask derived fromlengths, but_ConvolutionModuleignores it, in two ways:BatchNorm1d(default) andGroupNorm(use_group_norm=True) compute their mean/var over the time axis, so padded frames enter the statistics and skew the normalization applied to the valid ones.Minimal reproduction:
Observed:
Expected: a difference at float-precision level (~1e-6), since both calls process the same sequence and
lengthsmarks the padding. Withuse_group_norm=Truethe same script givesmax diff = 1.226370— larger than the mean output magnitude.Real-world impact. I hit this in practice (proshian/neural-swipe-typing, swipe-gesture decoding, where sequence lengths vary a lot within a batch). A Conformer encoder trained with large mixed-length batches had strong batched-validation metrics, but when switched to deployment-style inference (one sequence at a time, no padding) the metrics dropped dramatically. The model had learned to rely on padding-dependent behavior; nothing in the API suggests that the output of
Conformer(input, lengths)depends on batch composition.How other implementations handle this. Masking before the depthwise convolution and offering a padding-invariant normalization:
x = x.masked_fill(pad_mask.unsqueeze(1), 0.0)) and exposes anorm_typeoption includinglayer_norm: nemo/collections/asr/parts/submodules/conformer_modules.pybatch_norm/layer_norm: wenet/models/transformer/convolution.pyI have a backward-compatible fix (opt-in
conv_mask_paddingandconv_norm_typearguments; default behavior andstate_dictlayout unchanged) and will open a PR referencing this issue.Versions
Collecting environment information...
PyTorch version: 2.13.0+cpu
Is debug build: False
CUDA used to build PyTorch: None
ROCM used to build PyTorch: N/A
OS: Microsoft Windows 10 Pro (10.0.19045 64-bit)
GCC version: (rubenvb-4.6.3) 4.6.3
Clang version: 17.0.1
CMake version: version 4.2.1
Libc version: N/A
Python version: 3.12.9 (main, Feb 12 2025, 14:52:31) [MSC v.1942 64 bit (AMD64)] (64-bit runtime)
Python platform: Windows-10-10.0.19045-SP0
Is CUDA available: False
CUDA runtime version: No CUDA
CUDA_MODULE_LOADING set to: N/A
GPU models and configuration: No CUDA
Nvidia driver version: No CUDA
cuDNN version: No CUDA
Is XPU available: False
HIP runtime version: N/A
MIOpen runtime version: N/A
Is XNNPACK available: False
Caching allocator config: N/A
CPU:
Name: Intel(R) Core(TM) i3-6100U CPU @ 2.30GHz
Manufacturer: GenuineIntel
Family: 206
Architecture: 9
ProcessorType: 3
DeviceID: CPU0
CurrentClockSpeed: 2301
MaxClockSpeed: 2301
L2CacheSize: 512
L2CacheSpeed: None
Revision: 19971
Versions of relevant libraries:
[pip3] numpy==2.5.1
[pip3] torch==2.13.0+cpu
[pip3] torchaudio==2.11.0+cpu
[conda] No relevant packages