| from typing import Tuple |
| import pytest |
| import torch |
| import mel_spectrogram |
|
|
|
|
| def torch_mel_spectrogram( |
| samples: torch.Tensor, |
| filters: torch.Tensor, |
| fft_size: int, |
| fft_step: int, |
| n_frames: int, |
| ) -> torch.Tensor: |
| """Native PyTorch implementation of mel spectrogram generation.""" |
| |
| frames = [] |
|
|
| |
| for i in range(n_frames): |
| start = i * fft_step |
| end = min(start + fft_size, samples.size(0)) |
| if end - start < fft_size: |
| |
| frame = torch.zeros(fft_size, device=samples.device) |
| frame[: end - start] = samples[start:end] |
| frames.append(frame) |
| else: |
| frames.append(samples[start:end]) |
|
|
| |
| frames = torch.stack(frames) |
|
|
| |
| window = torch.hann_window(fft_size, device=samples.device) |
| windowed = frames * window |
|
|
| |
| fft_complex = torch.fft.rfft(windowed, dim=1) |
| fft_magnitudes = torch.abs(fft_complex) |
|
|
| |
| mel_spec = torch.matmul(fft_magnitudes, filters.T) |
|
|
| |
| eps = 1e-10 |
| mel_spec = torch.log10(torch.clamp(mel_spec, min=eps)) |
|
|
| |
| max_val = mel_spec.max() |
| min_val = max_val - 8.0 |
| mel_spec = torch.clamp(mel_spec, min=min_val) / 4.0 + 1.0 |
|
|
| |
| return mel_spec.T |
|
|
|
|
| @pytest.mark.parametrize( |
| "n_samples, n_mel, fft_size, fft_step, seed", |
| [ |
| (16000, 80, 1024, 512, 42), |
| (32000, 40, 1024, 256, 123), |
| (8000, 60, 512, 256, 987), |
| ], |
| ) |
| def test_mel_spectrogram( |
| n_samples: int, n_mel: int, fft_size: int, fft_step: int, seed: int |
| ) -> None: |
| """Test the CUDA mel_spectrogram function against a native PyTorch implementation.""" |
| if not torch.cuda.is_available(): |
| pytest.skip("CUDA not available") |
|
|
| |
| torch.manual_seed(seed) |
| torch.cuda.manual_seed(seed) |
|
|
| device = "cuda" |
|
|
| |
| n_frames = n_samples // fft_step |
| n_fft = 1 + fft_size // 2 |
|
|
| |
| samples = torch.randn(n_samples, dtype=torch.float32, device=device) |
| filters = torch.abs(torch.randn(n_mel, n_fft, dtype=torch.float32, device=device)) |
| filters = filters / filters.sum(dim=1, keepdim=True) |
|
|
| |
| cuda_output = torch.zeros(n_mel, n_frames, dtype=torch.float32, device=device) |
| mel_spectrogram.mel_spectrogram( |
| cuda_output, |
| samples, |
| filters, |
| fft_size, |
| fft_step, |
| ) |
|
|
| |
| actual_frames = cuda_output.shape[1] |
|
|
| |
| torch_output = torch_mel_spectrogram( |
| samples, filters, fft_size, fft_step, actual_frames |
| ) |
|
|
| |
| assert cuda_output.shape == torch_output.shape |
|
|
| |
| mae = torch.abs(cuda_output - torch_output).mean().item() |
| print(f"Mean Absolute Error: {mae}") |
|
|
| |
| assert not torch.isnan(cuda_output).any(), "CUDA output contains NaN values" |
| assert not torch.allclose(cuda_output, torch.zeros_like(cuda_output)) |
|
|
|
|