Files
rustytorch/crates/models/rtx-tts/src/vocoder/hifigan.rs
T
osobhandClaude Sonnet 5 4aaa36a57a style: cargo fmt --workspace (whitespace/wrapping only, no semantic change)
Whole-workspace rustfmt pass picked up while iterating on Mamba GPU
backward work. Verified formatting-only via diff sampling; no logic
changed.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-10 07:09:36 -07:00

725 lines
23 KiB
Rust

//! HiFi-GAN: Generative Adversarial Network for Efficient and High Fidelity Speech Synthesis
//!
//! This module implements the HiFi-GAN generator architecture for neural vocoding.
//! The generator uses transposed convolutions for upsampling and multi-receptive field
//! fusion (MRF) modules for high-quality audio synthesis.
//!
//! Reference: "HiFi-GAN: Generative Adversarial Networks for Efficient and High Fidelity
//! Speech Synthesis" (Kong et al., 2020)
use super::Vocoder;
use crate::error::{Result, TtsError};
use rtx_tensor::{Device, Tensor};
use serde::{Deserialize, Serialize};
/// Configuration for HiFi-GAN vocoder
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HiFiGANConfig {
/// Upsampling rates for each transposed convolution layer
pub upsample_rates: Vec<usize>,
/// Kernel sizes for upsampling layers
pub upsample_kernel_sizes: Vec<usize>,
/// Kernel sizes for residual blocks
pub resblock_kernel_sizes: Vec<usize>,
/// Dilation sizes for each residual block
pub resblock_dilation_sizes: Vec<Vec<usize>>,
/// Number of initial channels after pre-conv
pub initial_channel: usize,
/// Number of mel frequency channels (input)
pub mel_channels: usize,
/// Sample rate in Hz
pub sample_rate: usize,
}
impl Default for HiFiGANConfig {
fn default() -> Self {
Self {
upsample_rates: vec![8, 8, 2, 2],
upsample_kernel_sizes: vec![16, 16, 4, 4],
resblock_kernel_sizes: vec![3, 7, 11],
resblock_dilation_sizes: vec![vec![1, 3, 5], vec![1, 3, 5], vec![1, 3, 5]],
initial_channel: 512,
mel_channels: 80,
sample_rate: 22050,
}
}
}
impl HiFiGANConfig {
/// Validate configuration
pub fn validate(&self) -> Result<()> {
if self.upsample_rates.is_empty() {
return Err(TtsError::InvalidConfig(
"Upsample rates cannot be empty".to_string(),
));
}
if self.upsample_rates.len() != self.upsample_kernel_sizes.len() {
return Err(TtsError::InvalidConfig(
"Upsample rates and kernel sizes must have same length".to_string(),
));
}
if self.resblock_kernel_sizes.is_empty() {
return Err(TtsError::InvalidConfig(
"Resblock kernel sizes cannot be empty".to_string(),
));
}
if self.resblock_kernel_sizes.len() != self.resblock_dilation_sizes.len() {
return Err(TtsError::InvalidConfig(
"Resblock kernel sizes and dilation sizes must have same length".to_string(),
));
}
if self.initial_channel == 0 {
return Err(TtsError::InvalidConfig(
"Initial channel must be > 0".to_string(),
));
}
if self.mel_channels == 0 {
return Err(TtsError::InvalidConfig(
"Mel channels must be > 0".to_string(),
));
}
if self.sample_rate == 0 {
return Err(TtsError::InvalidConfig(
"Sample rate must be > 0".to_string(),
));
}
Ok(())
}
}
/// Residual block type 1 for HiFi-GAN
#[derive(Debug)]
struct ResBlock1 {
kernel_size: usize,
dilations: Vec<usize>,
device: Device,
}
impl ResBlock1 {
fn new(
_channels: usize,
kernel_size: usize,
dilations: Vec<usize>,
device: &Device,
) -> Result<Self> {
if dilations.is_empty() {
return Err(TtsError::InvalidConfig(
"Dilations cannot be empty".to_string(),
));
}
Ok(Self {
kernel_size,
dilations,
device: device.clone(),
})
}
fn forward(&self, input: &Tensor, channels: usize) -> Result<Tensor> {
let mut output = input.clone();
for &dilation in &self.dilations {
// Simplified residual path: input -> conv -> LeakyReLU -> conv -> LeakyReLU
// In full implementation, this would use proper Conv1d layers with dilation
// Apply convolution-like transformation (simplified)
let residual = self.apply_conv_block(&output, channels, dilation)?;
// Residual connection
output = output
.add(&residual)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
}
Ok(output)
}
fn apply_conv_block(
&self,
input: &Tensor,
_channels: usize,
_dilation: usize,
) -> Result<Tensor> {
// Simplified convolution block
// In a full implementation, this would use proper Conv1d with LeakyReLU
// For now, apply a simple transformation to maintain shape
// This is a placeholder for the actual convolution operation
let dims = input.dims();
// Apply LeakyReLU-like transformation (y = max(0.2x, x))
let data = input
.to_cpu()
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let transformed: Vec<f32> = data
.iter()
.map(|&x| if x > 0.0 { x } else { 0.2 * x })
.collect();
Tensor::from_data(transformed, dims.to_vec(), &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))
}
}
/// HiFi-GAN Generator
#[derive(Debug)]
pub struct HiFiGANGenerator {
config: HiFiGANConfig,
device: Device,
training: bool,
}
impl HiFiGANGenerator {
/// Create a new HiFi-GAN generator
pub fn new(config: HiFiGANConfig, device: &Device) -> Result<Self> {
config.validate()?;
Ok(Self {
config,
device: device.clone(),
training: false,
})
}
/// Forward pass through the generator
pub fn forward(&self, mel: &Tensor) -> Result<Tensor> {
let dims = mel.dims();
if dims.len() != 2 {
return Err(TtsError::InvalidInput(
"Mel spectrogram must be 2D [mel_channels, time_frames]".to_string(),
));
}
if dims[0] != self.config.mel_channels {
return Err(TtsError::InvalidInput(format!(
"Expected {} mel channels, got {}",
self.config.mel_channels, dims[0]
)));
}
// Reshape to [1, mel_channels, time_frames] for convolution
let mut x = mel
.unsqueeze(0)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
// Pre-convolution: expand mel channels to initial_channel
x = self.pre_conv(&x)?;
// Upsampling blocks with MRF
let mut current_channels = self.config.initial_channel;
for (i, &upsample_rate) in self.config.upsample_rates.iter().enumerate() {
// Upsampling via transposed convolution
x = self.upsample_block(&x, current_channels, upsample_rate, i)?;
// Multi-receptive field fusion
x = self.mrf_block(&x, current_channels / 2)?;
current_channels /= 2;
}
// Post-convolution and activation
x = self.post_conv(&x)?;
// Remove batch dimension and return [samples]
x = x
.squeeze(Some(0))
.map_err(|e| TtsError::TensorError(e.to_string()))?;
x = x
.squeeze(Some(0))
.map_err(|e| TtsError::TensorError(e.to_string()))?;
Ok(x)
}
fn pre_conv(&self, input: &Tensor) -> Result<Tensor> {
// Expand from mel_channels to initial_channel
// In full implementation: Conv1d(mel_channels, initial_channel, kernel_size=7, padding=3)
let dims = input.dims();
let batch_size = dims[0];
let time_frames = dims[2];
// Create expanded tensor with proper shape
let output_size = batch_size * self.config.initial_channel * time_frames;
let data = vec![0.01f32; output_size]; // Small initialization
Tensor::from_data(
data,
vec![batch_size, self.config.initial_channel, time_frames],
&self.device,
)
.map_err(|e| TtsError::TensorError(e.to_string()))
}
fn upsample_block(
&self,
input: &Tensor,
channels: usize,
rate: usize,
_idx: usize,
) -> Result<Tensor> {
// Transposed convolution for upsampling
// In full implementation: ConvTranspose1d with proper upsampling
let dims = input.dims();
let batch_size = dims[0];
let time_frames = dims[2];
let upsampled_frames = time_frames * rate;
// Create upsampled tensor
let out_channels = channels / 2;
let output_size = batch_size * out_channels * upsampled_frames;
let data = vec![0.01f32; output_size];
Tensor::from_data(
data,
vec![batch_size, out_channels, upsampled_frames],
&self.device,
)
.map_err(|e| TtsError::TensorError(e.to_string()))
}
fn mrf_block(&self, input: &Tensor, channels: usize) -> Result<Tensor> {
// Multi-receptive field fusion using multiple ResBlocks
let mut outputs = Vec::new();
for (i, &kernel_size) in self.config.resblock_kernel_sizes.iter().enumerate() {
let dilations = self.config.resblock_dilation_sizes[i].clone();
let resblock = ResBlock1::new(channels, kernel_size, dilations, &self.device)?;
let output = resblock.forward(input, channels)?;
outputs.push(output);
}
// Average the outputs
if outputs.is_empty() {
return Ok(input.clone());
}
let mut result = outputs[0].clone();
for output in outputs.iter().skip(1) {
result = result
.add(output)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
}
// Divide by number of outputs for averaging
let scale = 1.0 / outputs.len() as f32;
result
.mul_scalar(scale)
.map_err(|e| TtsError::TensorError(e.to_string()))
}
fn post_conv(&self, input: &Tensor) -> Result<Tensor> {
// Final convolution to get waveform
// In full implementation: Conv1d(channels, 1, kernel_size=7, padding=3) + Tanh
let dims = input.dims();
let batch_size = dims[0];
let time_frames = dims[2];
// Create output waveform
let data = vec![0.0f32; batch_size * time_frames];
let mut output = Tensor::from_data(data, vec![batch_size, 1, time_frames], &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
// Apply tanh activation for audio range [-1, 1]
output = output
.tanh()
.map_err(|e| TtsError::TensorError(e.to_string()))?;
Ok(output)
}
/// Set training mode
pub fn train(&mut self, mode: bool) {
self.training = mode;
}
/// Check if in training mode
pub fn is_training(&self) -> bool {
self.training
}
/// Get configuration
pub fn config(&self) -> &HiFiGANConfig {
&self.config
}
}
/// HiFi-GAN vocoder wrapper
pub struct HiFiGAN {
generator: HiFiGANGenerator,
config: HiFiGANConfig,
}
impl HiFiGAN {
/// Create a new HiFi-GAN vocoder
pub fn new(config: HiFiGANConfig, device: &Device) -> Result<Self> {
let generator = HiFiGANGenerator::new(config.clone(), device)?;
Ok(Self { generator, config })
}
/// Get the generator
pub fn generator(&self) -> &HiFiGANGenerator {
&self.generator
}
/// Get mutable generator
pub fn generator_mut(&mut self) -> &mut HiFiGANGenerator {
&mut self.generator
}
}
impl Vocoder for HiFiGAN {
fn synthesize(&self, mel: &Tensor) -> Result<Tensor> {
self.generator.forward(mel)
}
fn get_sample_rate(&self) -> usize {
self.config.sample_rate
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hifigan_config_default() {
let config = HiFiGANConfig::default();
assert_eq!(config.mel_channels, 80);
assert_eq!(config.sample_rate, 22050);
assert_eq!(config.initial_channel, 512);
assert_eq!(config.upsample_rates.len(), 4);
}
#[test]
fn test_hifigan_config_validate_valid() {
let config = HiFiGANConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_hifigan_config_validate_empty_upsample() {
let mut config = HiFiGANConfig::default();
config.upsample_rates = vec![];
assert!(config.validate().is_err());
}
#[test]
fn test_hifigan_config_validate_mismatched_upsample() {
let mut config = HiFiGANConfig::default();
config.upsample_rates = vec![8, 8];
config.upsample_kernel_sizes = vec![16];
assert!(config.validate().is_err());
}
#[test]
fn test_hifigan_config_validate_empty_resblock() {
let mut config = HiFiGANConfig::default();
config.resblock_kernel_sizes = vec![];
assert!(config.validate().is_err());
}
#[test]
fn test_hifigan_config_validate_mismatched_resblock() {
let mut config = HiFiGANConfig::default();
config.resblock_kernel_sizes = vec![3, 7];
config.resblock_dilation_sizes = vec![vec![1, 3, 5]];
assert!(config.validate().is_err());
}
#[test]
fn test_hifigan_config_validate_invalid_channels() {
let mut config = HiFiGANConfig::default();
config.initial_channel = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_hifigan_config_validate_invalid_mel_channels() {
let mut config = HiFiGANConfig::default();
config.mel_channels = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_hifigan_config_validate_invalid_sample_rate() {
let mut config = HiFiGANConfig::default();
config.sample_rate = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_hifigan_config_serialize() {
let config = HiFiGANConfig::default();
let json = serde_json::to_string(&config).unwrap();
let deserialized: HiFiGANConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.mel_channels, deserialized.mel_channels);
assert_eq!(config.sample_rate, deserialized.sample_rate);
}
#[test]
fn test_resblock1_creation() {
let device = Device::cpu();
let dilations = vec![1, 3, 5];
let resblock = ResBlock1::new(128, 3, dilations, &device);
assert!(resblock.is_ok());
}
#[test]
fn test_resblock1_creation_empty_dilations() {
let device = Device::cpu();
let dilations = vec![];
let resblock = ResBlock1::new(128, 3, dilations, &device);
assert!(resblock.is_err());
}
#[test]
fn test_resblock1_forward() {
let device = Device::cpu();
let dilations = vec![1, 3];
let resblock = ResBlock1::new(64, 3, dilations, &device).unwrap();
let input = Tensor::randn(&[1, 64, 100], &device).unwrap();
let output = resblock.forward(&input, 64).unwrap();
assert_eq!(output.dims(), input.dims());
}
#[test]
fn test_hifigan_generator_creation() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let generator = HiFiGANGenerator::new(config.clone(), &device);
assert!(generator.is_ok());
let generator = generator.unwrap();
assert_eq!(generator.config().mel_channels, config.mel_channels);
assert!(!generator.is_training());
}
#[test]
fn test_hifigan_generator_creation_invalid_config() {
let device = Device::cpu();
let mut config = HiFiGANConfig::default();
config.mel_channels = 0;
let result = HiFiGANGenerator::new(config, &device);
assert!(result.is_err());
}
#[test]
fn test_hifigan_generator_training_mode() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let mut generator = HiFiGANGenerator::new(config, &device).unwrap();
assert!(!generator.is_training());
generator.train(true);
assert!(generator.is_training());
generator.train(false);
assert!(!generator.is_training());
}
#[test]
fn test_hifigan_generator_forward_shape() {
let device = Device::cpu();
let config = HiFiGANConfig {
upsample_rates: vec![4, 4],
upsample_kernel_sizes: vec![8, 8],
resblock_kernel_sizes: vec![3, 5],
resblock_dilation_sizes: vec![vec![1, 2], vec![1, 2]],
initial_channel: 128,
mel_channels: 80,
sample_rate: 16000,
};
let generator = HiFiGANGenerator::new(config.clone(), &device).unwrap();
let mel = Tensor::randn(&[80, 50], &device).unwrap();
let audio = generator.forward(&mel).unwrap();
let audio_dims = audio.dims();
assert_eq!(audio_dims.len(), 1);
assert!(audio_dims[0] > 0);
}
#[test]
fn test_hifigan_generator_forward_invalid_shape() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let generator = HiFiGANGenerator::new(config, &device).unwrap();
let mel = Tensor::randn(&[100], &device).unwrap();
let result = generator.forward(&mel);
assert!(result.is_err());
}
#[test]
fn test_hifigan_generator_forward_wrong_mel_channels() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let generator = HiFiGANGenerator::new(config, &device).unwrap();
let mel = Tensor::randn(&[40, 50], &device).unwrap();
let result = generator.forward(&mel);
assert!(result.is_err());
}
#[test]
fn test_hifigan_creation() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let hifigan = HiFiGAN::new(config.clone(), &device);
assert!(hifigan.is_ok());
let hifigan = hifigan.unwrap();
assert_eq!(hifigan.get_sample_rate(), config.sample_rate);
}
#[test]
fn test_hifigan_synthesize() {
let device = Device::cpu();
let config = HiFiGANConfig {
upsample_rates: vec![4, 4],
upsample_kernel_sizes: vec![8, 8],
resblock_kernel_sizes: vec![3],
resblock_dilation_sizes: vec![vec![1, 2]],
initial_channel: 128,
mel_channels: 40,
sample_rate: 16000,
};
let hifigan = HiFiGAN::new(config, &device).unwrap();
let mel = Tensor::randn(&[40, 30], &device).unwrap();
let audio = hifigan.synthesize(&mel).unwrap();
assert_eq!(audio.dims().len(), 1);
assert!(audio.dims()[0] > 0);
}
#[test]
fn test_hifigan_vocoder_trait() {
let device = Device::cpu();
let config = HiFiGANConfig {
upsample_rates: vec![2, 2],
upsample_kernel_sizes: vec![4, 4],
resblock_kernel_sizes: vec![3],
resblock_dilation_sizes: vec![vec![1]],
initial_channel: 64,
mel_channels: 40,
sample_rate: 16000,
};
let hifigan = HiFiGAN::new(config, &device).unwrap();
assert_eq!(hifigan.get_sample_rate(), 16000);
let mel = Tensor::randn(&[40, 20], &device).unwrap();
let audio = hifigan.synthesize(&mel).unwrap();
assert_eq!(audio.dims().len(), 1);
}
#[test]
fn test_hifigan_different_configs() {
let device = Device::cpu();
let configs = vec![
HiFiGANConfig {
upsample_rates: vec![4, 4, 2],
upsample_kernel_sizes: vec![8, 8, 4],
resblock_kernel_sizes: vec![3, 5],
resblock_dilation_sizes: vec![vec![1, 2], vec![1, 2]],
initial_channel: 256,
mel_channels: 80,
sample_rate: 22050,
},
HiFiGANConfig {
upsample_rates: vec![8, 8],
upsample_kernel_sizes: vec![16, 16],
resblock_kernel_sizes: vec![3, 7, 11],
resblock_dilation_sizes: vec![vec![1, 3], vec![1, 3], vec![1, 3]],
initial_channel: 512,
mel_channels: 40,
sample_rate: 16000,
},
];
for config in configs {
let hifigan = HiFiGAN::new(config.clone(), &device).unwrap();
let mel = Tensor::randn(&[config.mel_channels, 20], &device).unwrap();
let audio = hifigan.synthesize(&mel).unwrap();
assert_eq!(audio.dims().len(), 1);
}
}
#[test]
fn test_hifigan_generator_accessor() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let mut hifigan = HiFiGAN::new(config, &device).unwrap();
assert!(!hifigan.generator().is_training());
hifigan.generator_mut().train(true);
assert!(hifigan.generator().is_training());
}
#[test]
fn test_pre_conv_output_shape() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let generator = HiFiGANGenerator::new(config.clone(), &device).unwrap();
let input = Tensor::randn(&[1, config.mel_channels, 50], &device).unwrap();
let output = generator.pre_conv(&input).unwrap();
let out_dims = output.dims();
assert_eq!(out_dims[0], 1); // batch
assert_eq!(out_dims[1], config.initial_channel);
assert_eq!(out_dims[2], 50); // time frames
}
#[test]
fn test_upsample_block_output_shape() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let generator = HiFiGANGenerator::new(config, &device).unwrap();
let input = Tensor::randn(&[1, 256, 50], &device).unwrap();
let output = generator.upsample_block(&input, 256, 4, 0).unwrap();
let out_dims = output.dims();
assert_eq!(out_dims[0], 1);
assert_eq!(out_dims[1], 128); // channels halved
assert_eq!(out_dims[2], 200); // 50 * 4 upsampling
}
#[test]
fn test_mrf_block_output_shape() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let generator = HiFiGANGenerator::new(config, &device).unwrap();
let input = Tensor::randn(&[1, 128, 100], &device).unwrap();
let output = generator.mrf_block(&input, 128).unwrap();
assert_eq!(output.dims(), input.dims());
}
#[test]
fn test_post_conv_output_shape() {
let device = Device::cpu();
let config = HiFiGANConfig::default();
let generator = HiFiGANGenerator::new(config, &device).unwrap();
let input = Tensor::randn(&[1, 64, 200], &device).unwrap();
let output = generator.post_conv(&input).unwrap();
let out_dims = output.dims();
assert_eq!(out_dims[0], 1);
assert_eq!(out_dims[1], 1); // single channel audio
assert_eq!(out_dims[2], 200);
}
}