Amphion developer on retainer: VITS fine-tuned on word-list recording inserts 40–60 ms inter-word pause at every word boundary — word-list dataset trains alignment model to treat word boundaries as silence — continuous speech re-recording eliminates artifact — 3 weeks — Amphion developer on monthly retainer

Amphion (open-mmlab/Amphion) unified speech generation framework; VITS recipe • Python 3.11; PyTorch 2.1.x • multilingual TTS service • fine-tuned on 2 hours of Spanish-accented English • every word boundary: 40–60 ms near-silence inter-word pause inserted by fine-tuned model • base VITS (before fine-tuning): no inter-word pauses • synthesized speech sounds like non-fluent word-by-word reading • developer checks Amphion fine-tuning configuration, VITS alignment parameters, text preprocessing for 3 weeks • root cause: 2-hour dataset was recorded as a word-list reading task (each word isolated, with natural inter-word silence between words); VITS monotonic alignment search (MAS) learns word boundary positions as silence boundaries in the mel spectrogram; fine-tuned VITS generates inter-word silence at every word boundary because that is the statistical pattern in the training data • fix: re-record 20 minutes of continuous paragraph reading (not word-lists) from same speaker; fine-tune only on this continuous speech subset • inter-word silence: 40–60 ms → 2–8 ms • MOS: 3.1 → 3.9 • 1.5h retainer engagement

Northfield Amphion TTS Ltd. built a multilingual TTS service using Amphion’s VITS recipe to create a custom voice model for a Spanish-accented English speaker, targeting professional narration use cases including e-learning content and corporate training materials. The team fine-tuned the base VITS model on 2 hours of recordings from the target speaker — a large dataset by micro-budget fine-tuning standards. Speaker identity similarity was high, confirming the fine-tuned voice model closely matched the target speaker. However, the synthesized speech had a significant naturalness problem: every word boundary in the output was accompanied by a 40–60 ms near-silence pause, creating a rhythm that sounded like a non-fluent speaker reading words one at a time rather than natural continuous speech. The artifact was present uniformly across all synthesized texts regardless of sentence structure or word frequency.

Comparing with the base VITS model (before fine-tuning): the base model produced continuous speech with no inter-word pauses, confirming the artifact was introduced by the fine-tuning process rather than being inherent to VITS. The team varied the Amphion fine-tuning configuration — different learning rates, batch sizes, number of training steps, alignment constraint parameters — without resolving the problem. The inter-word pause remained consistently at 40–60 ms across all configurations. After three weeks of investigation, they engaged an Amphion retainer developer who identified that the 2-hour recording dataset had been recorded as a word-list reading session, and the VITS alignment model had learned from that data that word boundaries were acoustic silence boundaries.

The Amphion VITS fine-tuning pipeline at the time of the engagement:

# Northfield Amphion TTS Ltd. — VITS fine-tuning pipeline
# Amphion (open-mmlab/Amphion), Python 3.11, PyTorch 2.1.x
# Use case: custom Spanish-accented English voice for e-learning TTS

import os
from amphion.models.tts.vits import VITS
from amphion.data import TTSDataset
from amphion.training import TTSTrainer

# Fine-tuning dataset: 2 hours of Spanish-accented English recordings
# Bug: dataset was recorded as a word-list reading task
#
# Recording session design (how the 2-hour dataset was collected):
#   Session type: word-list reading
#   Script format: list of individual words, one per line
#   Recording process: speaker read each word aloud, waited for the recorder
#                      to advance to the next word, then read the next word
#   Inter-word acoustic content: 50–150 ms near-silence between each word
#   Total words: ~6,000 words in 2 hours
#
# This is a common recording session design for TTS datasets because:
# - Easy to align (each file contains exactly one word or short phrase)
# - Easy to quality-check (each recording independently auditable)
# - Easy to record (no long takes to manage)
#
# However, it creates a systematic training data artifact:
# Every word boundary in the training data has an acoustic silence
# because the recording format explicitly inserts silence between words.

# VITS Monotonic Alignment Search (MAS) learning on word-list data:
#
# MAS optimizes the alignment A(text_tokens, mel_frames) to maximize
# the evidence lower bound (ELBO) of the VITS model.
#
# In the training data:
# text: "quality" [k w æ l ɪ t i]  → mel: [voiced voiced voiced voiced voiced voiced voiced] SILENCE
# text: "control" [k ə n t r oʊ l] → mel: [SILENCE voiced voiced voiced voiced voiced voiced]
#
# MAS learns: word-final phonemes are followed by silence frames
#             word-initial phonemes are preceded by silence frames
# → at inference: generates 40–60 ms silence at every word boundary

# Dataset configuration
dataset = TTSDataset(
    data_dir="/data/northfield-spanglish-recordings",  # Bug: word-list recordings
    metadata_file="metadata.csv",  # Format: filename|text (one word per file)
    sample_rate=22050,
    n_mel_channels=80,
)

# Fine-tuning configuration
config = {
    "model": "vits",
    "base_model": "amphion/vits-libritts960",  # Pre-trained on continuous speech
    "fine_tune_steps": 50000,
    "batch_size": 16,
    "learning_rate": 1e-4,
    "mas_temperature": 1.0,  # Tried varying this — did not help
}

# Diagnosis: inter-word silence duration measurement
# Method: forced alignment of synthesized output, measuring silence frames
#         at word boundaries in 100-utterance test set
#
# Synthesized word boundary silence (fine-tuned VITS):
#   Sentence: "The quality of the presentation improved significantly."
#   Word boundaries measured: 7 (between each word pair)
#   Mean inter-word silence: 51.3 ms (σ = 8.4 ms)
#   Range: 40–65 ms per boundary
#
# Base VITS (before fine-tuning):
#   Same sentence, 7 word boundaries
#   Mean inter-word silence: 6.2 ms (σ = 2.1 ms)
#   Range: 3–10 ms per boundary (normal coarticulation transition time)
#
# Perceptual impact (MOS evaluation):
#   Fine-tuned VITS: MOS 3.1 (σ=0.5) — "robotic", "word-by-word", "unnaturally slow"
#   Base VITS: MOS 4.1 (σ=0.3) — natural sounding
#   Fine-tuned with same speaker identity: MOS 3.1 (voice correct, rhythm wrong)

print("Root cause: word-list recording session — word boundaries = acoustic silences")
print("VITS MAS alignment model learns: word boundaries are associated with silence frames")
print("Fine-tuned VITS generates 40–60 ms silence at every word boundary at inference")

Why VITS alignment model learns word boundary silence from word-list recordings

VITS’s monotonic alignment search (MAS) is a differentiable dynamic programming algorithm that learns the correspondence between input phoneme sequences and output mel spectrogram frames during training, without requiring pre-computed forced alignments. MAS optimizes the alignment jointly with the rest of the VITS model by maximizing the evidence lower bound (ELBO) of a variational autoencoder. The alignment is constrained to be monotonic — each phoneme maps to one or more consecutive mel frames, and the mapping proceeds strictly left to right — but the specific timing (how many frames each phoneme occupies) is learned from the training data.

When the training data consists of word-list recordings, every word boundary in the training corpus has an acoustic silence of 50–150 ms between the word-final phoneme and the word-initial phoneme of the next word. This silence is an artifact of the recording session format: the speaker paused between reading each word to allow the recording operator to advance to the next word on the script. MAS, optimizing on this training data, learns that word boundaries are acoustically associated with silence frames. The learned alignment model encodes this as a timing pattern: word-final phonemes are followed by near-silence frames, and word-initial phonemes are preceded by near-silence frames. At inference, the VITS decoder generates mel spectrogram frames following these learned timing patterns, producing 40–60 ms of near-silence at every word boundary in the synthesized output, regardless of the natural prosodic structure of the input sentence.

The fix requires changing the training data, not the VITS model or fine-tuning configuration. Word-list recordings cannot be used to teach VITS natural continuous speech timing, because the word-list format encodes silence at word boundaries by design. The solution is to re-record 20 minutes of the same speaker reading continuous prose paragraphs — connected text without deliberate pauses between words. Twenty minutes of continuous speech contains approximately 2,400–3,000 word transitions, which is sufficient for MAS to learn natural continuous speech word boundary timing. Fine-tuning on only this 20-minute continuous speech dataset, discarding the original 2-hour word-list dataset, eliminates the inter-word pause artifact entirely.

# Fix: replace word-list recording with continuous speech recording
# Re-record 20 minutes of continuous paragraph reading from same speaker
# Fine-tune VITS only on continuous speech subset

import os
from amphion.models.tts.vits import VITS
from amphion.data import TTSDataset
from amphion.training import TTSTrainer

# Validation: check training data for inter-word silence artifacts
# before committing to a full fine-tuning run

import torchaudio
import numpy as np

SILENCE_THRESHOLD_DBFS = -40.0  # dBFS RMS; below = near-silence
MIN_WORD_BOUNDARY_SILENCE_MS = 15  # ms; above = word-list artifact

def measure_training_data_word_boundary_silences(metadata_csv, audio_dir):
    """
    Scan training data for word-list recording artifacts.
    Returns list of (filename, mean_boundary_silence_ms) for suspicious files.

    For multi-word utterances: measure inter-word silence by analyzing
    energy dips between word-final and word-initial phonemes (approximate,
    using RMS energy in 10ms windows).
    """
    import csv

    suspicious = []
    with open(metadata_csv) as f:
        reader = csv.reader(f, delimiter='|')
        for row in reader:
            if len(row) < 2:
                continue
            filename, text = row[0], row[1]
            words = text.strip().split()
            if len(words) < 2:
                continue  # Single-word files always have silence; skip

            audio_path = os.path.join(audio_dir, filename)
            if not os.path.exists(audio_path):
                continue

            wav, sr = torchaudio.load(audio_path)
            wav_numpy = wav.squeeze().numpy()

            # Measure inter-word boundary silence using 10ms window RMS
            frame_size = int(sr * 0.010)  # 10ms frames
            n_frames = len(wav_numpy) // frame_size
            rms_per_frame = []
            for i in range(n_frames):
                frame = wav_numpy[i * frame_size:(i + 1) * frame_size]
                rms = np.sqrt(np.mean(frame ** 2))
                rms_per_frame.append(20 * np.log10(rms + 1e-10))

            # Find silence dips (approximate word boundaries)
            silence_durations = []
            in_silence = False
            silence_start = 0
            for i, rms_db in enumerate(rms_per_frame):
                if rms_db < SILENCE_THRESHOLD_DBFS and not in_silence:
                    in_silence = True
                    silence_start = i
                elif rms_db >= SILENCE_THRESHOLD_DBFS and in_silence:
                    in_silence = False
                    duration_ms = (i - silence_start) * 10
                    if duration_ms >= 5:  # Filter out very short transitions
                        silence_durations.append(duration_ms)

            if silence_durations:
                mean_silence = np.mean(silence_durations)
                if mean_silence > MIN_WORD_BOUNDARY_SILENCE_MS:
                    suspicious.append((filename, mean_silence))

    return suspicious

# Apply validation to the original 2-hour word-list dataset
suspicious_files = measure_training_data_word_boundary_silences(
    "/data/northfield-spanglish-recordings/metadata.csv",
    "/data/northfield-spanglish-recordings/wavs/"
)
print(f"Suspicious files (mean word-boundary silence > {MIN_WORD_BOUNDARY_SILENCE_MS} ms): "
      f"{len(suspicious_files)} / total")

# Output: Suspicious files: 5,847 / 6,023 total
# → 97% of files have word-list recording artifacts → dataset must be replaced

# Re-recorded dataset: 20 minutes of continuous paragraph reading
# Recording script: 5 paragraphs × 4 minutes = 20 minutes connected prose
# Same speaker, same microphone, same recording environment as original

continuous_dataset = TTSDataset(
    data_dir="/data/northfield-spanglish-continuous",  # Fix: continuous speech
    metadata_file="metadata.csv",  # Format: filename|sentence (paragraph sentences)
    sample_rate=22050,
    n_mel_channels=80,
)

# Fine-tune only on continuous speech (20 minutes)
# Note: 20 min >> word-list 2h in terms of learning word boundary timing
# Word transitions in 20 min continuous speech: ~2,800 natural transitions
# Word-list 2h: ~6,000 isolated words, 0 natural transitions

config_fixed = {
    "model": "vits",
    "base_model": "amphion/vits-libritts960",
    "fine_tune_steps": 20000,  # Fewer steps: 20 min dataset smaller but cleaner
    "batch_size": 16,
    "learning_rate": 1e-4,
    "mas_temperature": 1.0,
}

# ---- Results after continuous speech re-recording ----
#
# Synthesized word boundary silence (re-fine-tuned VITS on continuous speech):
#   Sentence: "The quality of the presentation improved significantly."
#   Word boundaries: 7
#   Mean inter-word silence: 4.8 ms (σ = 1.9 ms)  ← natural coarticulation level
#   Range: 2–8 ms per boundary
#
# (Before fix: mean 51.3 ms, range 40–65 ms)
#
# MOS evaluation (20-listener crowdsource, 50 utterances per condition):
#   Word-list fine-tune: MOS 3.1 (σ=0.5) — "word-by-word reading"
#   Continuous-speech fine-tune: MOS 3.9 (σ=0.3) — "natural fluent speech"
#
# Speaker identity preserved:
#   Speaker cosine similarity vs target speaker:
#     Word-list fine-tune: 0.934 (high — speaker identity captured despite rhythm artifact)
#     Continuous-speech fine-tune: 0.941 (slightly higher — rhythm improvement helps SI)
#
# Total dataset used for successful fine-tune: 20 minutes (not 2 hours)
# Key insight: recording session design matters more than dataset size for VITS

print("Fix: continuous speech recording — natural word boundary timing")
print("Inter-word silence: 40–60 ms → 2–8 ms; MOS: 3.1 → 3.9")
print("Speaker cosine similarity: 0.934 → 0.941")

Related retainer posts


Track retainer hours without a client portal

HourTab turns your time-tracker CSV into a public URL your client bookmarks. No login. No portal. CSV in, URL out.

See pricing →