∇
Chapter 37 · Section 37.2 · 100 min

Neural vocoders

A vocoder turns a spectrogram back into sound. The acoustic models of §37.1 stop one step short of audio. They output a log-mel spectrogram, a grid with one column of 80 numbers for every 11.6 ms of speech, where each number is the logarithm of how much energy the sound has in one band of frequencies. A loudspeaker needs a waveform instead, 22,050 numbers per second, each one the air pressure at one instant. The vocoder is the model that goes from the grid to the waveform.

This last stage decides much of how natural a voice sounds and much of what speaking costs. Tacotron 2 (Shen et al. 2018) trained one acoustic model to predict spectrograms and turned them into sound in two ways, first with a classical algorithm and then with a neural vocoder. Listeners rated each clip's naturalness from 1 to 5, and the average rating, the mean opinion score (MOS), rose from 3.944 to 4.510. The original recordings scored 4.582. Speed varies even more. On one GPU, the slowest neural vocoder in a standard benchmark takes 52 minutes to produce ten seconds of speech, and the one that sounds best takes 0.06 seconds.

This section explains both numbers. First we say exactly what a vocoder must invent, and why a network trained to predict the waveform with a squared error cannot invent it. Then we build the two designs that work. WaveNet writes the waveform one sample at a time, the way a language model writes text, which sounds excellent and is slow. HiFi-GAN writes every sample at once, and a set of critic networks that listen at several periods and time scales teaches it what real speech sounds like. We follow the sentence Dr. Smith paid $5. from §37.1 through the generator with every shape, count the arithmetic behind real-time speed, and find where the audible artifacts come from. The neural audio codecs of §37.3 reuse almost all of it.

What a vocoder has to invent

Start with how the vocoder's input was made (§35.2 and §35.3 build it in full). A waveform x[n]x[n] is a list of samples taken fsf_s times a second, each a number in [−1,1][-1, 1]. The analysis cuts it into overlapping windows of NN samples and starts a new window every RR samples, a step called the hop. A Fourier transform turns each window, called a frame, into N/2+1N/2 + 1 complex numbers X[t,k]X[t, k], one for each frequency bin kk of frame tt.

A complex number has a size and an angle. The size, the magnitude ∣X[t,k]∣\lvert X[t, k]\rvert, says how strong that frequency is in the frame. The angle, the phase, says where that frequency's wave is in its cycle at the start of the frame. The analysis keeps only the magnitudes. It then pools the N/2+1N/2 + 1 magnitudes of a frame into nmelsn_{\text{mels}} overlapping triangular bands, narrow at low frequencies and wide at high ones. That spacing, the mel scale, matches how finely human hearing resolves pitch. Last, it takes a logarithm. With N=1024N = 1024 and nmels=80n_{\text{mels}} = 80, each frame goes from 513 complex numbers, which is 1,026 real ones, down to 80.

Python · cell 1

In plain words, a vocoder undoes an analysis that threw information away. Many waveforms have the same mel spectrogram, and the vocoder must pick one that a listener accepts.

The phase is the larger loss. The classical way to recover it is the Griffin-Lim algorithm, which §35.2 derived. It starts from any phase and repeats two steps. It builds the waveform whose STFT is closest to the current complex array, then keeps that waveform's phases and puts back the target magnitudes. No round makes the mismatch worse, but the algorithm stalls at a phase that sounds metallic. Tacotron 2's score of 3.944 was Griffin-Lim working from full linear-frequency spectrograms, 1,025 magnitudes per frame. From a mel spectrogram it would first have to guess those magnitudes from 80 numbers.

Why a regression cannot do it

A natural idea is to train a network to predict the waveform from the mel with a squared error. It fails because of a fact about averages. Take a 200 Hz tone, sin⁡(2π⋅200 t)\sin(2\pi\cdot 200\,t), and the same tone shifted by half a cycle, −sin⁡(2π⋅200 t)-\sin(2\pi\cdot 200\,t). They have identical magnitudes in every STFT bin, so identical mel spectrograms, and they sound the same. If a training set held both equally often, the best squared-error prediction would be their average, which is zero. The network would answer this mel spectrogram with silence.

Speech is the same story with many harmonics. A sustained vowel is a sum of harmonics, sinusoids at whole multiples of a fundamental frequency F0F_0. Changing the phases of the harmonics leaves every STFT magnitude, and so the mel spectrogram, essentially unchanged, and it barely changes how the vowel sounds. But it changes the waveform completely. The cell draws 200 vowels with the same harmonics and random phases. It checks that their magnitude spectrograms agree, that the waveforms themselves do not, and how loud their average is.

Python · cell 2

The 200 magnitude spectrograms agree to 0.1 percent of their peak. The waveforms' correlations range from about −0.8-0.8 to +0.8+0.8 around a mean of zero. Their average has 22 dB less power than any one of them, so a squared-error vocoder would answer this vowel with something close to silence. The same averaging, one level up, is why acoustic models trained with squared error predict oversmoothed spectrograms (§37.1), which Tacotron 2's authors point out.

So the waveform given the mel is a distribution with many separate peaks, and the vocoder must be a generative model, one that samples or commits to one plausible waveform rather than averaging them. Two answers work. One models the distribution explicitly, one sample at a time (WaveNet). The other trains a second network, a critic, to decide what counts as a real waveform (GANs). Both reappear inside every neural audio codec of §37.3. An interviewer who asks why a vocoder cannot be trained with an L2 loss on samples wants exactly this argument.

WaveNet writes one sample at a time

The first answer treats a waveform the way a language model treats text. A language model writes a sentence one token at a time, each drawn from a probability distribution given the tokens before it. WaveNet (van den Oord et al. 2016) writes a waveform x=(x[1],…,x[Ns])\mathbf{x} = (x[1], \dots, x[N_s]) one sample at a time in the same way. The chain rule of probability makes this exact,

p(x∣c)=∏n=1Nsp(x[n]∣x[1],…,x[n−1], c),p(\mathbf{x}\mid\mathbf{c}) = \prod_{n=1}^{N_s} p\big(x[n] \mid x[1], \dots, x[n-1],\ \mathbf{c}\big),

where c\mathbf{c} is the conditioning, the extra input the waveform depends on. For a vocoder it is the mel frames and possibly a speaker.

In plain words, the probability of a whole waveform is the probability of the first sample, times that of the second given the first, and so on. Training maximizes the log-likelihood with teacher forcing. Every sample is predicted from the true samples before it, so all positions are trained at once, in parallel. Generation cannot be parallel. It samples x[1]x[1], feeds it back, samples x[2]x[2], and so on, NsN_s times. Two design questions remain. What is the distribution over one sample, and what network sees enough of the past to predict it?

One sample is one of 256 classes

A sample is a real number, so the obvious output is a continuous density such as a Gaussian. WaveNet's authors chose a softmax over discrete values instead, as in their earlier image models, which generate a picture one pixel at a time (van den Oord et al. 2016). A categorical distribution assumes nothing about the shape of the next sample's distribution, which can be skewed, heavy-tailed, or have several peaks, and which changes from one moment of speech to the next. But a 16-bit sample takes 65,536 values, and a 65,536-way softmax at every sample is expensive. So WaveNet first compresses each sample to 8 bits, 256 classes, with a quantizer borrowed from telephony.

The idea is to put more levels near zero, where speech spends most of its time. With 256 evenly spaced levels the step is 2/256=0.00782/256 = 0.0078. A quiet sample of 0.01 is rounded to 0.0078, a 22 percent error, while a loud sample of 0.5 is represented exactly. Mu-law companding (compressing, then expanding) moves the levels so that both errors are under 2 percent. It bends each sample with a logarithmic curve, quantizes the result evenly, and undoes the curve when decoding.

The inverse follows in two lines. If y=Fμ(x)y = F_\mu(x), then ∣y∣ln⁡(1+μ)=ln⁡(1+μ∣x∣)\lvert y\rvert\ln(1+\mu) = \ln(1 + \mu\lvert x\rvert), so (1+μ)∣y∣=1+μ∣x∣(1+\mu)^{\lvert y\rvert} = 1 + \mu\lvert x\rvert, and xx has the sign of yy. The minimum in the class formula matters only at x=1x = 1, which would otherwise land in a class 256 that does not exist.

That last sentence can be made exact. Recall from §35.1 that a uniform quantizer with step Δ\Delta adds an error that behaves like uniform noise of power Δ2/12\Delta^2/12. The signal-to-quantization-noise ratio (SQNR) is the signal's power divided by that noise power, in decibels. For a full-scale sine it is 6.02 nbits+1.766.02\,n_{\text{bits}} + 1.76 dB. Because the noise stays the same size, the SQNR falls one decibel for every decibel the signal gets quieter. Mu-law makes the noise shrink with the signal.

In plain words, mu-law is uniform quantization of the logarithm of the amplitude. Away from zero its cells have a fixed relative width, 4.3 percent of the amplitude at 8 bits, so the error is at most about 2.2 percent of the sample. The price is paid at the top. There mu-law's cells are about 5.6 times wider than linear ones (Δx(1)/Δy=256ln⁡256/255=5.57\Delta_x(1)/\Delta_y = 256\ln 256/255 = 5.57). That is why a full-scale signal gets 38 dB from mu-law against 50 dB from linear coding.

∂ Proof that mu-law's SQNR does not depend on the level

The decoded sample is x^=Fμ−1(Fμ(x)+ey)\hat x = F_\mu^{-1}(F_\mu(x) + e_y). To first order, x^−x≈(Fμ−1)′(Fμ(x)) ey=ey/Fμ′(x)\hat x - x \approx (F_\mu^{-1})'(F_\mu(x))\,e_y = e_y/F_\mu'(x), by the inverse function rule. Differentiating the definition for x≠0x \ne 0 gives Fμ′(x)=μ/((1+μ∣x∣)ln⁡(1+μ))F_\mu'(x) = \mu/\big((1 + \mu\lvert x\rvert)\ln(1+\mu)\big). A cell of width Δy\Delta_y therefore maps to a cell of width Δy/Fμ′(x)\Delta_y/F_\mu'(x), which proves the step formula.

Given xx, the error power is E[ey2]/Fμ′(x)2=(Δy2/12) (1+μ∣x∣)2ln⁡2(1+μ)/μ2=(Δy2/12)ln⁡2(1+μ) (∣x∣+1/μ)2\mathbb{E}[e_y^2]/F_\mu'(x)^2 = (\Delta_y^2/12)\,(1 + \mu\lvert x\rvert)^2\ln^2(1+\mu)/\mu^2 = (\Delta_y^2/12)\ln^2(1+\mu)\,(\lvert x\rvert + 1/\mu)^2. Averaging over xx and dividing E[x2]\mathbb{E}[x^2] by the result gives the SQNR formula. ∎

For a sine of amplitude AA, E[x2]=A2/2\mathbb{E}[x^2] = A^2/2 and E∣x∣=2A/π\mathbb{E}\lvert x\rvert = 2A/\pi, so the second factor is (A2/2)/(A2/2+4A/(πμ)+1/μ2)(A^2/2)/(A^2/2 + 4A/(\pi\mu) + 1/\mu^2). It is −0.04-0.04 dB at A=1A = 1, −0.4-0.4 dB at A=0.1A = 0.1, and −3.6-3.6 dB at A=0.01A = 0.01.

The cell checks the theorem with a simulation. It quantizes sines with mu-law and with 8-bit linear coding, at levels from full scale down to −50-50 dB. Each level is the sine's peak in dBFS, decibels relative to full scale, 20log⁡10A20\log_{10}A.

Python · cell 3

Mu-law holds between 34 and 39 dB from full scale down to −40-40 dBFS, within a decibel of the theory. Eight-bit linear coding falls from 49 dB to 11 dB, and at −50-50 dBFS it rounds the whole sine to zero (0 dB, the error is the signal). The two cross between −10-10 and −20-20 dBFS. Speech has a wide dynamic range. Its quiet syllables, consonants, and decays sit tens of decibels below its peaks, exactly where mu-law wins by 8 to 30 dB.

The widget draws one stretch of a sine through both 8-bit coders, with each coder's output levels as ticks at the edges, beside the SQNR curves of the cell. Lower the peak level and count the linear levels left inside the sine.

Why does 8-bit mu-law beat 8-bit linear coding for quiet speech?
A sine and its two reconstructions. The ticks at the edges are the levels each coder can output.
Loading interactive figure…

Eight bits still limit quality, since 38 dB of SQNR can be heard as faint hiss in quiet passages. The successors kept a discrete output at 16 bits without paying for a 65,536-way softmax.

Warm-up

Mu-law in and out

Implement the mu-law quantizer of the definition box with μ+1\mu + 1 classes. The compressor is Fμ(x)=sign⁡(x)ln⁡(1+μ∣x∣)/ln⁡(1+μ)F_\mu(x) = \operatorname{sign}(x)\ln(1+\mu\lvert x\rvert)/\ln(1+\mu), which maps [−1,1][-1, 1] onto itself. Split [−1,1][-1, 1] into μ+1\mu + 1 equal cells, numbered 0 to μ\mu from the left. The class of xx is the cell that contains Fμ(x)F_\mu(x), and x=1x = 1 belongs to the top cell, μ\mu.

  • mulaw_encode(x, mu=255) maps an array of samples in [−1,1][-1, 1] to an integer array of classes in {0,…,μ}\{0, \dots, \mu\}.
  • mulaw_decode(q, mu=255) maps classes back to samples. It takes the center of each class's cell and applies the exact inverse of FμF_\mu.

The logarithm's base cancels in the ratio ln⁡(1+μ∣x∣)/ln⁡(1+μ)\ln(1+\mu\lvert x\rvert)/\ln(1+\mu), so np.log10 works as well as np.log.

Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.

Dilated causal convolutions see far

The network must read the past samples and output a distribution for the next one. During training it must do so for all NsN_s positions at once. A causal convolution does both. It is a convolution whose output at time nn reads only inputs at time nn and earlier, so it never sees the future and still runs over the whole sequence in one pass. Giving it a dilation dd spaces its taps dd steps apart, so the same few weights reach further back.

Here is the payoff on a tiny example. Three causal layers with kernel 2 and no dilation let one output see 4 input samples. With dilations 1, 2, and 4 the same three layers see 8, and each further layer with a doubled dilation doubles the view again. The number of input steps that one output can depend on is the receptive field, which §0.8 defined for any stack of convolutions. Here is the version we need.

In plain words, each layer adds (k−1)(k-1) times its dilation to how far back the output can see. Doubling the dilations makes the view grow exponentially with depth, while the cost grows only linearly.

∂ Proof of the receptive field

By induction on the number of layers. Before any layer, output nn of the identity depends on input nn only, a window of 1=1+(k−1)⋅01 = 1 + (k-1)\cdot 0. Suppose after l−1l - 1 layers every unit at time mm depends on inputs in [m−Dl−1,m][m - D_{l-1}, m] with Dl−1=(k−1)∑j<ldjD_{l-1} = (k-1)\sum_{j \lt l} d_j. Layer ll combines the units at times n,n−dl,…,n−(k−1)dln, n - d_l, \dots, n - (k-1)d_l, whose windows together cover [n−(k−1)dl−Dl−1,n][n - (k-1)d_l - D_{l-1}, n], so Dl=Dl−1+(k−1)dlD_l = D_{l-1} + (k-1)d_l. A residual connection adds the unit at time nn of the layer below, whose window is already inside, and pointwise nonlinearities change no windows. This is the recursion rl=rl−1+(kl−1)dl jl−1r_l = r_{l-1} + (k_l - 1)d_l\,j_{l-1} of §0.8 with every jump j=1j = 1, since nothing is strided. The geometric sum ∑l=0L−12l=2L−1\sum_{l=0}^{L-1}2^l = 2^L - 1 gives the doubling case. ∎

The same table holds a surprise. A 12-layer WaveNet with two cycles of dilations 1 to 32 sees only 253 samples (10.5 ms) and still scores 4.481, against 4.526 for the 30-layer baseline. Thirty undilated layers (61 samples, 2.5 ms) fall to 3.930. A vocoder conditioned on mel frames does not need a long memory, because the frames already carry the slow structure, such as the pitch contour, the phonemes, and the loudness. It needs context at the scale of a few pitch periods, enough to make each period consistent with the last. This is why the fast vocoders later in the section get away with modest receptive fields. It is also the answer when an interviewer asks how much context a vocoder needs.

The cell checks causality with an impulse, a single 1 in a row of zeros. Then it measures receptive fields by pushing an impulse through random positive weights, so that nothing cancels, and compares them with the theorem.

Python · cell 4

An impulse at n=10n = 10 reaches outputs 10 and 14 (the current tap, and the tap 4 back as seen from 14) and nothing earlier. The measured fields match the formula, including WaveNet's 3,070.

Not every schedule fills its window. Kernel 2 with dilations 1, 3, 9 spans 14 samples, but a path from input to output takes one tap per layer, so it can only step back by sums of 0 or 1, 0 or 3, and 0 or 9. It reaches 8 of the 14 offsets, and the other inputs can never influence the output. These gaps are the one-dimensional version of the gridding that dilated image networks suffer. The common schedules avoid them. Doubling dilations with kernel 2 fill every offset, and so does MelGAN's schedule 1, 3, 9 with kernel 3 (Kumar et al. 2019), which sees all 27.

The widget builds a stack one layer at a time. Each new layer reads kk copies of the tree of taps below it, the copies shifted back by multiples of its dilation. Press Stack layers with dilations 1, 2, 4 and the copies land end to end, so the view doubles per layer. Switch to 1, 3, 9 with kernel 2 and they overshoot, leaving pink inputs that no path reaches. The box after it gives the exact rule.

How far back can a stack of dilated causal layers see?
Lines are the taps feeding the newest output. Each layer reads k of them, spaced by its dilation.
Loading interactive figure…

The gated residual block

WaveNet's layers are not bare convolutions. Each one wraps a dilated convolution in a small block with a gate and two paths out, borrowed from the gated PixelCNN (van den Oord et al. 2016). A gate multiplies a feature by a number between 0 and 1 that the network computes, so the network decides at every time step and channel how much of the feature to pass on.

In plain words, the tanh half proposes a feature and the sigmoid half decides how much of it to let through. The residual path carries the block's input forward unchanged plus a correction, which keeps 30 or more layers trainable (the argument of §5.1). The skip path lets every layer, short-range and long-range, speak directly to the output. The input is shifted right by one step so that output nn never sees sample nn, which makes the network a next-sample predictor rather than a copy machine.

Conditioning comes in two kinds. Global conditioning is one vector for the whole utterance, such as a speaker embedding espk\mathbf{e}_{\text{spk}}, added through Vespk\mathbf{V}\mathbf{e}_{\text{spk}} at every time step. Local conditioning is a slower sequence, here the mel frames at fs/Rf_s/R frames per second, which must be brought up to the sample rate first. WaveNet learned this upsampling with a stack of transposed convolutions and found that repeating each frame RR times worked slightly worse. Tacotron 2's vocoder upsamples its 12.5 ms frames with two upsampling layers in its conditioning stack.

Interview

A WaveNet stack and its receptive field

The setup provides causal_conv1d(h, w, dilation), the dilated causal convolution of the definition box for h of shape (C, T) and w of shape (C_out, C, k). Each layer is a dictionary with the dilation under "d", the filter and gate weights "Wf" and "Wg" (shape (C, C, k)), the residual weights "Wr" (shape (C, C)), and the skip weights "Ws" (shape (S, C)). There is no conditioning.

  1. Implement wavenet_stack(h, layers). It runs the gated residual blocks in order on h of shape (C, T) and returns the summed skip outputs, shape (S, T).
  2. Implement measured_receptive_field(layers, C, T=64), which finds the receptive field by experiment. Perturb the input one time step at a time (all channels, by a random vector) and count how many input time steps change the skip output at the last time step T - 1. Use only wavenet_stack, not the formula, because the count is smaller than 1+(k−1)∑d1 + (k-1)\sum d when the dilations leave holes.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.

Generating is sequential, and caching makes each step cheap

Training is parallel, and generation is not. To sample x[n]x[n] the network needs x[n−1]x[n-1], which it has only just produced. So a WaveNet vocoder runs its whole network once per sample, one run after another, 24,000 times for one second of audio at 24 kHz.

A naive implementation also makes each run expensive, because it recomputes every layer over the whole receptive field. Most of that work was already done at the previous step. With kernel 2, layer ll at time nn reads its input at times nn and n−dln - d_l. So it is enough to keep, for each layer, a queue of its last dld_l inputs. Each step pushes one new column through the stack and reads one old column per layer from the queues. The work per sample is then proportional to the number of layers LL, instead of growing with the receptive field. Paine et al. (2016) call this Fast WaveNet.

The total cache is C∑ldl(k−1)C\sum_l d_l(k-1) numbers. It plays the role that the KV cache of §6.5 plays for a Transformer, the stored results of past steps that let each new step skip recomputing them. Unlike the KV cache, it never grows with the length of the utterance, because nothing older than the receptive field can matter.

Interview

Fast WaveNet generation with per-layer queues

The setup provides a complete greedy WaveNet over 16 classes. wavenet_skip(H, layers) runs the gated residual stack of the definition box, without conditioning, on an embedded sequence H of shape (C, T). Each layer is a dictionary with the dilation "d", filter and gate weights "Wf" and "Wg" of shape (C, C, k), and 1×11\times1 weights "Wr" of shape (C, C) and "Ws" of shape (S, C). Layer by layer it computes z=tanh⁡(Wf⊛dH)⊙σ(Wg⊛dH)\mathbf{z} = \tanh(\mathbf{W}_f\circledast_d\mathbf{H})\odot\sigma(\mathbf{W}_g\circledast_d\mathbf{H}) with the dilated causal convolution ⊛d\circledast_d, adds Ws @ z to the skip sum, and replaces H by H + Wr @ z. It returns the skip sum, shape (S, T). embed is a (16, C) table of class embeddings and Wout is a (16, S) readout. naive_generate(first, n, layers, embed, Wout) produces a sequence of n + 1 classes. At every step it recomputes the whole stack over the whole history and takes the argmax of Wout @ relu(skip[:, -1]). Implement the fast version in two functions.

  • wavenet_step(h_t, layers, queues) takes one embedded column h_t of shape (C,) and a list queues. Entry l of the list has shape (C, d_l * (k - 1)) and holds the last inputs of layer l, oldest first. The function returns (skip_t, new_queues), where skip_t has shape (S,) and equals the column of the full stack's skip output at this time step.
  • fast_generate(first, n, layers, embed, Wout) starts from all-zero queues. It must return exactly the sequence that naive_generate returns while storing only the queues.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.

Four ways around one sample at a time

Caching makes each step cheap, but it leaves 24,000 steps per second of audio in a strict sequence. On a GPU such a sequence is limited by memory traffic, not by arithmetic. Each step reads every weight once to produce one number, the regime §20.1 calls memory-bound decoding. Kalchbrenner et al. (2018) put numbers on it. A WaveNet with a 16-bit mixture output needs about 60 matrix-vector products in sequence per sample (30 residual blocks of two layers each). Even a small recurrent network of 3 million parameters, if it reloads its weights for each of 24,000 samples, moves 3×106×24×103×43\times10^6\times 24\times10^3\times 4 bytes, 288 GB per second of audio. That is more than a third of a P100 GPU's memory bandwidth before any computation.

Four families of answers followed. One makes each step tiny, with a single small recurrent layer instead of a deep stack (WaveRNN, LPCNet). One distills a trained WaveNet, training a fast student network to imitate it and generate all samples in one parallel pass (Parallel WaveNet). One uses a normalizing flow, an invertible network from noise to waveforms trained by exact likelihood (WaveGlow). And one uses diffusion, which turns noise into a waveform in a few parallel denoising passes (DiffWave, WaveGrad). The box gives each family's mechanism and numbers, and the table after it compares them.

A fifth answer, the GAN, won in practice. One forward pass of a convolutional generator, parallel over all samples, turned out to be enough to be both small and good. The rest of the section builds it.

FamilyExamplesSequential steps per second of audioQuality, as publishedTrainingOn a CPU
Sample-level autoregressionWaveNet, WaveRNN, LPCNetfsf_s (WaveRNN subscale fs/16f_s/16)WaveNet 4.41 at 24 kHz, 16 bitslikelihood, stable, simpleWaveNet no; WaveRNN and LPCNet yes, with sparsity or linear prediction
Distilled flowParallel WaveNetone pass per flow4.41, equal to its teacherneeds a trained teacher and extra lossesnot its target
Likelihood flowWaveGlowone pass3.81 in HiFi-GAN's testexact likelihood, large model0.21 times real time
DiffusionDiffWave, WaveGradone pass per denoising step (6 or more)DiffWave 4.44 against WaveNet 4.43regression loss, stableslow with many steps
Time-domain GANMelGAN, Parallel WaveGAN, HiFi-GAN, BigVGANone passHiFi-GAN V1 4.36 against 4.45 for recordingsadversarial plus reconstruction lossesHiFi-GAN V3 13.4 times real time on a laptop
GAN with a Fourier headiSTFTNet, Vocosone pass at the frame rate, then an inverse STFTVocos not significantly different from BigVGANadversarial plus reconstruction lossesVocos 170 times real time

The MOS values come from different listening tests, so compare them only within one row's source. The column that matters is the third. A GAN vocoder's work is not a chain of fsf_s dependent steps, so a GPU can apply it to all samples at once, and its speed is limited only by the arithmetic and memory of one pass.

GAN vocoders

A generative adversarial network (GAN) trains two networks against each other. A discriminator, which this section also calls a critic, looks at a waveform and outputs a score for how real it looks. A generator turns mel frames into a waveform and is trained to raise the critic's scores of its outputs. §32.6 built this game with a critic trained by cross-entropy. For a fixed generator, the best such critic outputs pdata/(pdata+pG)p_{\text{data}}/(p_{\text{data}} + p_G), where pdatap_{\text{data}} is the density of real examples and pGp_G that of generated ones. Against that critic the generator minimizes the Jensen-Shannon divergence, a symmetric measure of how different two distributions are that is zero only when they are equal. That section also showed that the original generator loss stops giving gradient once the critic is confident. It replaced it by the non-saturating loss and by the hinge loss, which stops pushing on an example once its score clears a margin.

The least-squares game

HiFi-GAN and Parallel WaveGAN use a third loss, least squares. (MelGAN, Vocos, and the codecs of §37.3 use the hinge loss.) The critic outputs an unbounded number and is trained as a regression, toward 1 on real audio and toward 0 on generated audio.

Is this a sensible game? If the critic were perfect, would the generator's best move be to produce real speech? The answer is yes, and the proof is short.

In plain words, against a perfect critic, nothing scores better than producing exactly the distribution of real speech. So the least-squares game is a legitimate way to fit a distribution, not only a heuristic.

∂ Proof of the optimal critic and the bound of one quarter

LD=∫[pdata(D−1)2+pGD2]dx\mathcal{L}_D = \int \big[p_{\text{data}}(D - 1)^2 + p_G D^2\big]d\mathbf{x} can be minimized pointwise. At each x\mathbf{x} the integrand is a parabola in DD with derivative 2pdata(D−1)+2pGD2p_{\text{data}}(D - 1) + 2p_GD and second derivative 2(pdata+pG)>02(p_{\text{data}} + p_G) \gt 0, so its minimum is at D=pdata/(pdata+pG)D = p_{\text{data}}/(p_{\text{data}} + p_G). Then D⋆−1=−pG/(pdata+pG)D^\star - 1 = -p_G/(p_{\text{data}} + p_G), and

EpG[(D⋆−1)2]=∫pG3(pdata+pG)2 dx=∫pdata (pG/pdata)3(1+pG/pdata)2 dx,\mathbb{E}_{p_G}\big[(D^\star - 1)^2\big] = \int \frac{p_G^3}{(p_{\text{data}} + p_G)^2}\,d\mathbf{x} = \int p_{\text{data}}\,\frac{(p_G/p_{\text{data}})^3}{(1 + p_G/p_{\text{data}})^2}\,d\mathbf{x},

which is the formula with r=pG/pdatar = p_G/p_{\text{data}}. Differentiating twice, φ′(r)=r2(r+3)/(1+r)3\varphi'(r) = r^2(r+3)/(1+r)^3 and φ′′(r)=6r/(1+r)4>0\varphi''(r) = 6r/(1+r)^4 \gt 0 for r>0r \gt 0, so φ\varphi is strictly convex. Because pdatap_{\text{data}} is a probability density, Jensen's inequality gives ∫pdata φ(pG/pdata)≥φ(∫pdata⋅pG/pdata)=φ(1)=14\int p_{\text{data}}\,\varphi(p_G/p_{\text{data}}) \ge \varphi\big(\int p_{\text{data}}\cdot p_G/p_{\text{data}}\big) = \varphi(1) = \tfrac14. Equality holds only if pG/pdatap_G/p_{\text{data}} is constant, and a constant ratio of two densities is 1. ∎

Mao et al. derive their theory for other targets, −1-1 for generated examples, 1 for real ones, and 0 for the generator, which make the generator's objective a Pearson χ2\chi^2 divergence. In their experiments they train with 0, 1, 1, the targets HiFi-GAN uses, which give the convex divergence above, with the same fixed point.

∂ When the generator puts mass where the data has none

Drop the assumption pdata>0p_{\text{data}} \gt 0. Let mm be the mass pGp_G puts where pdata=0p_{\text{data}} = 0. There D⋆=0D^\star = 0, so each unit of that mass costs the generator (0−1)2=1(0 - 1)^2 = 1, contributing mm. On the support of pdatap_{\text{data}}, Jensen as above gives at least φ(1−m)=(1−m)3/(2−m)2\varphi(1 - m) = (1-m)^3/(2-m)^2, since ∫pG=1−m\int p_G = 1 - m there. So the loss is at least g(m)=m+(1−m)3/(2−m)2g(m) = m + (1-m)^3/(2-m)^2. Its derivative is g′(m)=1−(1−m)2(4−m)/(2−m)3g'(m) = 1 - (1-m)^2(4-m)/(2-m)^3. Writing u=1−m∈[0,1]u = 1 - m \in [0, 1], (2−m)3−(1−m)2(4−m)=(1+u)3−u2(3+u)=1+3u>0(2-m)^3 - (1-m)^2(4-m) = (1+u)^3 - u^2(3+u) = 1 + 3u \gt 0, so g′>0g' \gt 0. The bound is smallest at m=0m = 0, where it is 14\tfrac14. Mass outside the data never helps, and the unique minimizer is still pG=pdatap_G = p_{\text{data}}.

What makes least squares useful in practice is its gradient. Take one generated waveform with score s=Disc⁡(G(Xmel))s = \operatorname{Disc}(G(\mathbf{X}_{\text{mel}})). The generator's loss (s−1)2(s - 1)^2 has derivative 2(s−1)2(s - 1), which is −2-2 for a sample the critic is sure is fake (s=0s = 0). It shrinks only as the score approaches the real target, so a confident critic still teaches the generator.

The cell checks the optimal discriminator on a grid of candidate outputs. Then it checks the bound 14\tfrac14 for a few one-dimensional Gaussian generators against a standard normal data distribution.

Python · cell 5

The grid minimizer agrees with pdata/(pdata+pG)p_{\text{data}}/(p_{\text{data}} + p_G) to within the grid step. The generator's loss is exactly 0.25 when it matches the data, larger for every mismatch in mean or width, and close to 1 when the two distributions barely overlap.

Critics that listen at several periods and scales

The theorem is about ideal critics. Real critics are networks, and what they can see decides what the generator learns. The HiFi-GAN paper (Kong, Kim & Bae 2020) started from an observation about speech. Voiced sounds are close to periodic, built from sinusoids with many different periods. A vocoder's typical failures, such as a buzz, a smeared pitch, or a phase jump from one pitch period to the next, are failures of that periodic structure. So the authors built a critic that looks at the waveform through several periods at once.

The trick is a reshape. Write the samples 0 to 8 into rows of 3, so the rows are (0, 1, 2), (3, 4, 5), and (6, 7, 8). Column 0 now holds samples 0, 3, 6, every third sample. A 2-D convolution that slides only down the columns judges each such strand as a signal of its own.

Four terms appear in the definitions below. A leaky ReLU is a ReLU that keeps a small slope (0.1 here) for negative inputs instead of zeroing them. A grouped convolution splits the channels into groups and lets each output channel read only its own group, which cuts the cost. Weight normalization writes each weight vector as a learned length times a unit direction, which makes training faster and steadier. Spectral normalization divides each weight matrix by its largest singular value, so the layer cannot stretch its input by more than a factor of 1. That keeps a critic from becoming too sharp to learn from.

In plain words, each sub-discriminator deals the waveform out into pp interleaved strands, every pp-th sample, and judges each strand as a signal at rate fs/pf_s/p. Every input sample lands in exactly one cell of the grid, so the critic's gradient reaches every sample of the generated waveform and nothing is averaged away.

The widget deals a short waveform into pp columns. For a pulse train that repeats every PP samples, every pulse lands in the same column exactly when pp divides PP, and each column then repeats every P/pP/p rows. A real vowel repeats only every hundred samples or more, longer than any of the periods, so each column is the same wave read at one sample in pp.

What does a period discriminator see when it reshapes audio by a period p?
The waveform is dealt row by row into p columns. Indigo is positive, pink negative. Click a column to light its samples above.
Loading interactive figure…

Why prime periods? Compare periods 2 and 4. Column 0 of period 4 holds samples 0, 4, 8, ..., all of which are already in column 0 of period 2 (0, 2, 4, 6, 8, ...). So a period-4 critic looks only at strands that the period-2 critic already sees in full. Now compare periods 2 and 3. Column 0 of period 3 holds 0, 3, 6, 9, 12, ..., and only 0, 6, 12, every other one, are in column 0 of period 2. In general, if p<p′p \lt p' share no factor, the Chinese remainder theorem says that a remainder on division by pp and a remainder on division by p′p' together fix the remainder on division by pp′pp'. So two such columns share one sample in every pp′pp', only a fraction 1/p1/p of the period-p′p' column.

Periods 2, 3, 5, 7, 11 share no factors in pairs, which is what the paper means by choosing them "to avoid overlaps as much as possible". Its ablation agrees, with a MOS of 3.90 for periods 2, 4, 8, 16, 32 against 4.10 for the primes.

Both are window-based critics. Each output score sees one receptive field of the waveform, and the loss averages over all of them. This is the audio version of the PatchGAN of §32.6, a critic that scores many local patches of an image instead of the whole image.

The two critics complement each other. The MSD looks at consecutive samples, but its pooling is a low-pass filter, so its coarser scales barely hear high frequencies. The MPD keeps every sample but judges strands of non-consecutive ones. The cell shows the reshape, counts the overlaps between periods, and measures what HiFi-GAN's pooling (kernel 4, stride 2, padding 2) keeps of a pure tone.

Python · cell 6

A 2 kHz tone keeps 65 percent of its power after one pooling and 8 percent after two, and a 5 kHz tone keeps about 1 percent after one. A 9 kHz tone keeps 6 percent after one pooling, and what is left is folded down to 2,025 Hz, because the pooled signal has half the sample rate. So high-frequency artifacts, such as the hiss of aliasing later in the section, are nearly invisible to the coarse MSD scales, and the little that gets through shows up at the wrong frequency. The MPD sees them in full.

The ablations make the point at scale. Removing the MPD dropped HiFi-GAN V3 from a MOS of 4.10 to 2.28, and adding an MPD to MelGAN raised it from 2.88 to 3.35. An interviewer who asks what HiFi-GAN contributed expects the MPD, with these numbers.

Reconstruction losses that ignore phase

A GAN alone learns slowly and can produce plausible speech that does not match the input mel. So every GAN vocoder adds a reconstruction loss, a direct comparison of its output with the real waveform. That loss must not repeat the mistake of the regression vocoder, so it compares magnitudes, never raw samples. HiFi-GAN uses the mel spectrogram itself,

Lmel(G)=E ∥ϕ(x)−ϕ(G(ϕ(x)))∥1,\mathcal{L}_{\text{mel}}(G) = \mathbb{E}\,\big\lVert\phi(\mathbf{x}) - \phi\big(G(\phi(\mathbf{x}))\big)\big\rVert_1 ,

the L1 distance between the log-mel of the real waveform and that of the generated one. Parallel WaveGAN (Yamamoto, Song & Kim 2020) introduced a loss at several STFT resolutions, which neural codecs reuse.

In plain words, the spectral convergence term is a relative error dominated by the loud bins. The log term weighs every bin equally, loud or quiet. Neither looks at phase. Several resolutions are needed because of the time-frequency trade-off of §35.2. A 50 ms window resolves harmonics but smears a plosive's onset over 50 ms, and a 10 ms window catches the onset but blurs the harmonics. A generator fit at one resolution can hide its errors in that resolution's blind spot.

The cell compares a reference voice (8 harmonics of 200 Hz over a faint noise floor) with four alternatives, under both spectral terms and under a plain waveform L1 loss.

Python · cell 7

Read the output row by row. New harmonic phases, which a listener barely notices, cost more under the waveform L1 than replacing the voice by white noise, while the spectral terms barely move. A missing harmonic, which a listener does notice, raises the spectral convergence sharply. A fresh noise floor 60 dB down is inaudible, yet it costs the log term more than the missing harmonic does, because the log term counts the thousands of quiet bins between harmonics as much as the loud ones. No generator can reproduce one particular noise realization from a mel spectrogram, so in quiet regions the log term has the same averaging problem as the waveform loss. That is why spectral losses sit next to an adversarial loss and never replace it.

Core

Multi-resolution STFT loss

The setup provides stft_mag(x, n_fft, win, hop), which returns the magnitude array of a 1-D signal, frames as rows, with a Hann window of length win centered in n_fft samples. Implement mr_stft_loss(x, y, configs, eps=1e-7) for a real signal x, a generated signal y, and a list of (n_fft, win, hop) tuples. For each resolution, compute the two terms of the definition box and add them.

  • The spectral convergence ∥S−S^∥F/(∥S∥F+ϵ)\lVert\mathbf{S} - \hat{\mathbf{S}}\rVert_F/(\lVert\mathbf{S}\rVert_F + \epsilon).
  • The log-magnitude term, the mean over all entries of ∣log⁡max⁡(S,ϵ)−log⁡max⁡(S^,ϵ)∣\lvert\log\max(\mathbf{S}, \epsilon) - \log\max(\hat{\mathbf{S}}, \epsilon)\rvert, with the natural logarithm.

Return the average of these sums over the resolutions, as a float.

Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.

HiFi-GAN's full objective

One more term completes the objective. The feature matching loss (Larsen et al. 2016, used for audio by MelGAN) compares the intermediate activations of each sub-discriminator on the real waveform and on the generated one. It turns the critic into a learned measure of how different two sounds are. It also gives the generator a dense, stable signal even when the critic's final scores say little.

In plain words, the generator is told three things at once. Sound real to eight critics. Produce the same internal features in those critics that real audio does. Match the input mel. The weight 45 balances terms of very different kinds, a per-element mean of log-mel errors against squared critic scores. The mel term ties the output to its input from the first step, so the adversarial terms only have to add realism. HiFi-GAN's ablation measured each piece on V3, one of its two small generators. Without the mel loss the MOS fell from 4.10 to 3.25, without the MPD to 2.28, and without the MSD to 3.74.

Interview

HiFi-GAN's discriminator plumbing and losses

Implement the pieces that sit between the networks and the optimizer. Discriminator outputs and features arrive as NumPy arrays.

  • mpd_reshape(x, p) takes x of shape (B, 1, T) and pads the time axis on the right to a multiple of p by reflection, with no padding if T already is one. Reflection mirrors the signal about its last sample without repeating it, as NumPy's and PyTorch's "reflect" modes do, so samples a,b,ca, b, c padded by two become a,b,c,b,aa, b, c, b, a. It returns shape (B, 1, T_pad // p, p), with element [b, 0, m, j] equal to the padded x[b, 0, m * p + j].
  • lsgan_d_loss(real_outs, fake_outs) takes two lists with one array of scores per sub-discriminator and returns ∑k[mean⁡((1−rk)2)+mean⁡(fk2)]\sum_k[\operatorname{mean}((1 - r_k)^2) + \operatorname{mean}(f_k^2)].
  • lsgan_g_loss(fake_outs) returns ∑kmean⁡((1−fk)2)\sum_k\operatorname{mean}((1 - f_k)^2).
  • feature_matching(real_feats, fake_feats) takes two lists with one entry per sub-discriminator, each a list of arrays, one per layer. It returns the sum over sub-discriminators and layers of the mean absolute difference within each layer.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.

The generator upsamples by the hop

HiFi-GAN's generator must turn 86 mel frames per second into 22,050 samples per second, a factor of 256, the hop. It does this in four stages, each of which stretches time by a fixed factor and then refines the result. The generator is fully convolutional, made only of convolutions, so it works on any number of frames.

Two building blocks need a sentence each. A transposed convolution with stride ss and kernel kk lets each input step stamp a learned pattern of kk values onto the output, each stamp shifted ss steps from the previous one, which multiplies the length by ss (§0.8). A multi-receptive-field fusion (MRF) module runs three residual blocks in parallel on the same input, with kernel sizes 3, 7, and 11, and averages their outputs, so short and long patterns are modeled side by side. Each of those blocks has three pairs of convolutions. In each pair the first convolution has dilation 1, 3, or 5, and the second has dilation 1. (The paper says the MRF sums the blocks, and the official code divides the sum by three.)

The V1 generator is then this chain, with a leaky ReLU (slope 0.1) in front of each upsampler and each convolution inside the MRF blocks.

  1. A 7-tap convolution lifts the 80 mel channels to 512.
  2. Four stages, each a transposed convolution that upsamples time and halves the channels, followed by an MRF. The upsampling rates are 8, 8, 2, 2, with kernels 16, 16, 4, 4 and padding (k−s)/2(k - s)/2, so the channels go 512, 256, 128, 64, 32.
  3. A final 7-tap convolution to one channel and a tanh produce the waveform in [−1,1][-1, 1].

Proof. The output length of a transposed convolution, from §0.8, is (n−1)s−2p+k(n - 1)s - 2p + k for padding pp, with no dilation or output padding. With p=(k−s)/2p = (k - s)/2 this is ns−s−(k−s)+k=nsns - s - (k - s) + k = ns. Length-preserving layers change nothing, and composing the stages multiplies the factors. ∎

Now follow the sentence Dr. Smith paid $5. from the start of the section through HiFi-GAN V1. Its 156 mel frames enter as an 80×15680\times156 array at 86 frames per second. Each stage multiplies the time axis by its stride and halves the channels, and the cell prints the shape after each one.

Python · cell 8

Checkerboards and tonal artifacts

§0.8 warned that a transposed convolution overlaps its stamps unevenly when the kernel size is not a multiple of the stride. Images then show a faint checkerboard (Odena, Dumoulin & Olah 2016). Here is the smallest case. With kernel 3 and stride 2, each input stamps 3 outputs and moves 2 to the right, so the outputs receive 2, 1, 2, 1, ... stamps in turn. In audio a pattern that repeats every few samples is not a picture but a tone, so it is worth knowing exactly when it happens.

∂ Proof of the overlap count

Input ii reaches output oo through tap u=o−isu = o - is, which must lie in {0,…,k−1}\{0, \dots, k-1\}. So the stamps on oo are the multiples isis of ss in the window of kk consecutive integers o−k+1,…,oo - k + 1, \dots, o, and for interior oo every such ii is a valid input index. Split the window into aa blocks of ss consecutive integers, each containing exactly one multiple of ss, and bb leftover integers. The leftovers contain one multiple of ss for exactly bb of the ss residues of oo modulo ss, and none for the others. So the count is aa or a+1a + 1, the latter at a fraction b/sb/s of positions, and it is constant if and only if b=0b = 0. Finally u=o−is≡o(mods)u = o - is \equiv o \pmod s, so with all inputs equal to 1, output oo is the sum of the taps u≡ou \equiv o, which depends only on o mod so \bmod s. ∎

In plain words, a stride-ss transposed convolution is ss separate filters, its polyphase components, whose outputs are interleaved. With an all-ones kernel the components differ only in how many taps they have, and that difference is the checkerboard. Choosing kk a multiple of ss fixes it, as HiFi-GAN (k=2sk = 2s) and MelGAN do.

But a trained kernel's components can have different gains even when k=2sk = 2s. Then a steady input comes out modulated with a period of ss samples. Its spectrum has lines at multiples of fout/sf_{\text{out}}/s, which is the input rate of that layer. So the first upsampler of a 22.05 kHz HiFi-GAN can leave a faint tone at multiples of 86 Hz, the frame rate. Pons et al. (2021) call these tonal artifacts. They show that nearest-neighbor upsampling followed by a convolution avoids them, since every output phase then uses the same filter, at the price of a mild low-pass effect.

The cell counts the stamps for four kernel and stride pairs, then runs a random kernel with k=2sk = 2s on a constant input.

Python · cell 9

With k=3k = 3, s=2s = 2 the counts alternate 2, 1, and with k=4k = 4 or 1616 against strides 2 and 8 they are flat. The random kernel with k=2sk = 2s still turns a constant input into a pattern of period 8 equal to its polyphase gains. That is the tonal artifact, which the counting argument alone cannot see. In a design review, "is kk a multiple of ss?" is the first question about an upsampler, and "are the polyphase gains equal?" is the second.

Core

Where checkerboards and tones come from

Implement three functions for a 1-D transposed convolution with stride s, kernel size k, and no padding. Input i adds x[i] * w to the outputs i * s through i * s + k - 1, so n_in inputs give (n_in - 1) * s + k outputs.

  • transposed_overlap(k, s, n_in) returns an integer array with the number of kernel taps that land on each output position when the kernel and the input are all ones.
  • ripple(k, s) uses n_in = 2 * k + 2 and returns the maximum minus the minimum of the counts over the interior positions, k - 1 to (n_in - 1) * s inclusive.
  • polyphase_gains(w, s) returns an array of s values for a constant input of ones and the kernel w. Entry r is the output value at the interior positions o with o % s == r.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.

Aliasing, and periodic activations

A second artifact comes from the activation functions. A pointwise nonlinearity applied at the sample rate turns a tone at frequency ff into harmonics at 2f,3f,…2f, 3f, \dots. Recall the folding formula of §35.1. A sampled signal cannot represent frequencies above the Nyquist frequency fs/2f_s/2, and a harmonic at f′f' above it folds back to ∣f′−mfs∣\lvert f' - mf_s\rvert for the nearest whole number mm. The folded harmonics land at frequencies that are not harmonics of anything in the signal, and the ear hears them as roughness or hiss.

The fix, taken from image generators (Karras et al. 2021), is to apply the nonlinearity at a higher rate. Upsample by 2 with a low-pass filter, apply the nonlinearity, low-pass at the original Nyquist frequency, and downsample by 2. The cell applies HiFi-GAN's leaky ReLU to a 3 kHz tone at 22.05 kHz, once directly and once at twice the rate, and measures the power that lands away from the tone's harmonics.

Python · cell 10

A leaky ReLU with slope 0.1 equals 0.55x+0.45∣x∣0.55x + 0.45\lvert x\rvert. The absolute value of a sine repeats every half period, so it adds only even harmonics, at 6, 12, 18, 24 kHz, and so on. At 22.05 kHz every harmonic above the Nyquist frequency of 11.025 kHz folds back. The one at 12 kHz lands at 10,050 Hz, 18 kHz at 4,050 Hz, 24 kHz at 1,950 Hz, and 30 kHz at 7,950 Hz. Together the aliases sit 25.5 dB below the signal. Running the same activation at twice the rate pushes them down to −42.7-42.7 dB. (Harmonics above 22.05 kHz still fold even at the doubled rate, which is why the improvement is finite.)

BigVGAN (Lee et al. 2023) builds this into every residual layer, and it also changes the activation itself.

In plain words, Snake is a straight line with a learnable ripple. It gives each channel a built-in oscillation at a frequency of its choosing, which suits a network whose output is a sum of oscillations, and the AMP keeps the ripple's harmonics from folding. Trained only on LibriTTS speech at 24 kHz, BigVGAN generalizes to unseen speakers, recording conditions, singing, and instrumental music without fine-tuning, which is what a universal vocoder means.

Skipping the upsampler with an inverse STFT

A time-domain generator spends most of its layers upsampling. It re-creates at 22,050 samples per second a structure that a Fourier transform could produce exactly. The alternative is to predict an STFT and let a fixed inverse STFT make the samples. iSTFTNet (Kaneko et al. 2022) replaced the last upsampling stages of HiFi-GAN by an inverse STFT, and Vocos (Siuzdak 2024) went all the way.

Vocos's backbone is a stack of ConvNeXt blocks, convolutional blocks borrowed from image classification (Liu et al. 2022), and it runs only at the frame rate, never changing the time resolution. For each frame its head outputs two vectors m,p∈RN/2+1\mathbf{m}, \mathbf{p} \in \R^{N/2+1}. The complex STFT coefficient of bin kk is exp⁡(mk)(cos⁡pk+isin⁡pk)\exp(m_k)(\cos p_k + i\sin p_k), and a fixed inverse STFT with the analysis hop turns the frames into a waveform. The exponential keeps magnitudes positive. Passing pkp_k through cosine and sine makes every real number a valid phase, so the network never has to model the jump at ±π\pm\pi that makes raw phase hard to regress.

Vocos trains with an MPD, a multi-resolution discriminator, and the hinge loss of §32.6. Its listening-test quality was not significantly different from BigVGAN's, and on a CPU it ran 170 times real time against 5.8 for HiFi-GAN in the same benchmark. Skipping the high-rate layers is where that factor of 30 comes from.

What it costs at real scale

A GAN vocoder is fast because all of its work is arithmetic that runs in parallel, and that arithmetic can be counted. A convolution from CinC_{\text{in}} to CoutC_{\text{out}} channels with kernel kk costs 2CinCoutk2C_{\text{in}}C_{\text{out}}k floating-point operations (FLOPs) per output time step, one multiply and one add per weight. A transposed convolution costs the same per input step. Each layer runs at its own rate, so its cost per second of audio is its cost per step times its steps per second. The cell counts HiFi-GAN V1 layer by layer.

Python · cell 11

HiFi-GAN V1 needs about 53 GFLOP per second of audio, 2.4 MFLOP per output sample. Almost all of it is in the MRF blocks, and the largest share is in the 128-channel blocks at 5.5 kHz. It is not in the biggest weight matrices, the 256-channel blocks at 689 Hz, which hold most of the 13.9 M parameters but run at a low rate.

A V100's peak of 15.7 TFLOP/s in 32-bit floats would allow about 300 times real time. So the measured 168 implies about 57 percent of peak, the efficiency of large, regular convolutions over many time steps at once. A WaveNet step is a handful of matrix-vector products for a single sample, a few percent of peak at best, repeated 22,050 times in sequence.

The arithmetic also says what to shrink for a CPU. The cost scales with channels squared, times kernel size, times rate. V2 cut the hidden width from 512 to 128 (0.92 M parameters, about 16 times fewer FLOPs, 9.7 times real time on the CPU), and V3 used fewer layers with wider dilations. Vocos removes the high-rate layers altogether, which is why its CPU speed is so far ahead.

One model from text to waveform

The two-stage pipeline, an acoustic model and then a vocoder, has a seam. The vocoder is trained on mels computed from real audio, and at inference it hears the acoustic model's smoother predictions. VITS (Kim, Kong & Son 2021) removes the seam by training both stages at once as a conditional variational autoencoder. Recall from §32.3 that a variational autoencoder (VAE) encodes data into a latent variable and decodes it back. It is trained by maximizing a lower bound on the likelihood, the ELBO, which is a reconstruction term minus a KL term that keeps the encoder's distribution close to a prior.

training:   linear spectrogram --posterior encoder--> z --HiFi-GAN decoder (on windows)--> waveform
                                                      |                                     |
            phonemes --text encoder--> Gaussians --(MAS alignment)--> prior on z via a flow  |
                                                                    L1 on mels, MPD + feature matching
inference:  phonemes --> text encoder --> stochastic durations --> sample prior --> inverse flow --> z --> decoder

In the diagram, a vocoder is still there. It is the HiFi-GAN V1 generator, decoding a learned latent z\mathbf{z} instead of a mel spectrogram, and it is trained with the same multi-period critic. On LJSpeech VITS scored 4.43 against 4.46 for the recordings, above Tacotron 2 with HiFi-GAN (3.77, or 4.25 with the vocoder fine-tuned on Tacotron 2's outputs) and above HiFi-GAN driven by Glow-TTS, a flow-based acoustic model (Kim et al. 2020), at 4.32 fine-tuned.

The real way, in PyTorch

The block below builds a six-layer gated WaveNet stack (kernel 2, dilations 1 to 32) and checks causality with autograd. The gradient of output nn with respect to every input after nn must be exactly zero, and its support must be the receptive field. It then builds a HiFi-GAN generator twice. A tiny one with rates 4, 4 maps a random (1,80,T)(1, 80, T) mel to (1,1,16T)(1, 1, 16T) samples, and the full V1 configuration is built only to count its parameters. Finally it runs one period sub-discriminator and computes the least-squares losses.

🔥 In PyTorchfor a machine with PyTorch

To use a trained vocoder, load published weights rather than training your own. Both calls below follow the libraries' own documentation as of 2026 (the vocos package and NVIDIA's BigVGAN repository). Each expects the mel configuration its model was trained on. Both models below use 100 bands at 24 kHz with hop 256, yet their mel formulas and floors differ, so each gets its mel from its own feature code. That is the first thing to check when a vocoder sounds wrong.

Where vocoders break

Train-test mismatch. A vocoder trained on mels of real audio meets an acoustic model's predictions at inference. Those are smoother, because the acoustic model was trained with a squared or absolute error and so predicts something close to an average. Tacotron 2 measured the cost. Its WaveNet trained on ground-truth mels and fed predicted ones scored 4.362, against 4.526 when trained on the predictions themselves. The fix is to train the vocoder on the acoustic model's teacher-forced predictions, made while the acoustic model is fed the true previous frames, so that they line up frame for frame with the real waveform. Tacotron 2 trained its WaveNet on such predictions from the start. HiFi-GAN fine-tuned an already trained V1 on them for 100,000 steps, and its MOS rose from 3.91 to 4.18 (ground truth 4.23 in that test).

Unseen conditions. A vocoder trained on one clean speaker generalizes only partly. HiFi-GAN V1 scored 3.77 against 3.79 for the recordings on nine held-out speakers of the VCTK corpus. But noisy recordings, singing, and music fall outside what a leaky-ReLU generator learned from one reader, which is BigVGAN's reason for periodic activations and large, diverse training data.

Characteristic artifacts. Each kind of artifact points to a cause.

What you hearLikely causeWhere it is fixed
metallic buzz, wavering pitchphase inconsistent from one pitch period to the nextthe multi-period critic
faint whine at the frame rate and its harmonicsunequal polyphase gains in an upsampler (tonal artifact)kk a multiple of ss, or nearest-neighbor upsampling
hiss or roughness in the high frequenciesaliasing from the activationsanti-aliased activations (BigVGAN)
muffled, smeared speechoversmoothed input melsfine-tuning on predicted mels

GAN training can also be unstable or drop modes, which is why the mel loss carries weight 45 and why BigVGAN needed gradient clipping at 112 M parameters.

Budgets. An on-device assistant has a CPU and tens of milliseconds, so it needs a V3-class model, LPCNet, or a Fourier-head vocoder. A podcast service on GPUs can afford V1 or Vocos, and a music application needs BigVGAN-class generalization. The last interview question below works through this choice.

Read more

Watch next

🎬 Building makemore Part 5: Building a WaveNet · Andrej KarpathyBuilds a WaveNet-style tree of dilated causal layers in PyTorch from scratch (over characters, not audio), the receptive-field idea this section derives, with the shape bookkeeping that trips people up.
🎬 SPCC 2020 - Junichi Yamagishi/Xin Wang - Neural auto-regressive, source-filter and glottal vocoders · ISCA SynSIGA long expert tutorial on autoregressive vocoders (WaveNet, WaveRNN, LPCNet) and neural source-filter models, background for the sample-by-sample half of this section.
🎬 SPCC 2020 - Sivanand Achanta/Vassilis Tsiaras - Basic components of neural vocoders. · ISCA SynSIGThe building blocks of neural vocoders (features, quantization, dilated convolutions, output distributions), good for a second pass over mu-law and the WaveNet stack.