Skip to content

Commit fa03a53

Browse files
committed
feat(bs_roformer): 支持零直流分量处理并优化频率点计算
-【优化】在 roformer_stft_freq_bins 中通过 torch.stft 计算频率点数,以保持模型初始化时的随机种子兼容性 -【功能】在 MelBandRoformer 中引入 zero_dc 参数,支持强制置零直流分量 -【功能】在 istft 过程中实现 PyTorch 和 MLX 的直流置零逻辑 -【测试】新增针对 zero_dc 功能的单元测试用例
1 parent 6431961 commit fa03a53

4 files changed

Lines changed: 46 additions & 1 deletion

File tree

pymss_core/modules/bs_roformer/common.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,15 @@ def init_roformer_stft(module, stft_n_fft, stft_hop_length, stft_win_length, stf
175175

176176

177177
def roformer_stft_freq_bins(module, window_length):
178-
return int(module.stft_kwargs["n_fft"]) // 2 + 1
178+
# The original training code computed this shape through torch.stft on a
179+
# random probe tensor during model construction. Preserve that RNG-consuming
180+
# behavior so scratch initialization remains seed-compatible.
181+
return torch.stft(
182+
torch.randn(1, 4096),
183+
**module.stft_kwargs,
184+
window=torch.ones(window_length),
185+
return_complex=True,
186+
).shape[1]
179187

180188

181189
def roformer_freqs_per_bands_with_complex(module, freqs_per_bands, freqs):
@@ -354,6 +362,8 @@ def istft_roformer(module, stft_repr, context, length):
354362
.permute(0, 1, 3, 2, 4)
355363
.reshape(b * n * context.channels, context.freq_bins, t)
356364
)
365+
if getattr(module, "zero_dc", False):
366+
stft_repr = stft_repr.index_fill(1, torch.tensor(0, device=stft_repr.device), 0.0)
357367

358368
try:
359369
recon_audio = torch.istft(

pymss_core/modules/bs_roformer/mel_band_roformer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(
4242
stft_win_length=2048,
4343
stft_normalized=False,
4444
stft_window_fn: Optional[Callable] = None,
45+
zero_dc=True,
4546
mask_estimator_depth=1,
4647
match_input_audio_length=False,
4748
mlp_expansion_factor=4,
@@ -116,6 +117,7 @@ def __init__(
116117
mask_estimator_kwargs={"mlp_hidden_layers": mlp_hidden_layers},
117118
)
118119

120+
self.zero_dc = zero_dc
119121
self.match_input_audio_length = match_input_audio_length
120122

121123
def _forward_mask_core(self, selected_stft_repr):

pymss_core/modules/bs_roformer/mlx_roformer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ def _istft_roformer(module, stft_repr, context, length):
116116
stft_repr = stft_repr.reshape(b, n, freq_bins, channels, t, 2)
117117
stft_repr = mx.transpose(stft_repr, (0, 1, 3, 2, 4, 5)).reshape(b * n * channels, freq_bins, t, 2)
118118
complex_stft = stft_repr[..., 0] + (1j * stft_repr[..., 1])
119+
if getattr(module, "zero_dc", False):
120+
complex_stft = complex_stft.at[:, 0, :].set(0)
119121
complex_stft = mx.moveaxis(complex_stft, -2, -1)
120122
if normalized:
121123
complex_stft = complex_stft * np.sqrt(n_fft)

tests/test_core_api.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pymss_core
66
from pymss_core import AttrDict, load_config, unwrap_state_dict
77
from pymss_core.modules._dsp import mel_filterbank
8+
from pymss_core.modules.bs_roformer.common import SpectralContext, istft_roformer
89

910

1011
def test_public_api_exports_core_functions():
@@ -49,6 +50,36 @@ def test_model_internal_dsp_helpers_do_not_require_librosa():
4950
assert filters.shape == (60, 1025)
5051

5152

53+
def test_roformer_istft_zero_dc(monkeypatch):
54+
class DummyModule:
55+
stft_kwargs = {}
56+
zero_dc = True
57+
58+
captured = {}
59+
60+
def fake_istft(stft_repr, **kwargs):
61+
captured["stft_repr"] = stft_repr.detach().clone()
62+
return torch.zeros(stft_repr.shape[0], kwargs["length"])
63+
64+
monkeypatch.setattr(torch, "istft", fake_istft)
65+
66+
context = SpectralContext(
67+
batch=1,
68+
channels=1,
69+
freq_bins=3,
70+
audio_length=4,
71+
stft_window=torch.ones(4),
72+
x_is_mps=False,
73+
)
74+
stft_repr = torch.ones(1, 1, 3, 2, dtype=torch.complex64)
75+
76+
output = istft_roformer(DummyModule(), stft_repr, context, length=4)
77+
78+
assert output.shape == (1, 1, 4)
79+
assert torch.equal(captured["stft_repr"][:, 0], torch.zeros_like(captured["stft_repr"][:, 0]))
80+
assert torch.equal(captured["stft_repr"][:, 1:], torch.ones_like(captured["stft_repr"][:, 1:]))
81+
82+
5283
def test_vr_network_structures_remain_importable():
5384
from pymss_core.modules.vocal_remover import CascadedASPPNet, CascadedNet, ModelParameters
5485

0 commit comments

Comments
 (0)