Writing
8 min read

Fine-Tuning XTTS v2 for More Natural Bible Narration

A technical case study in improving Bible narration with XTTS v2 through dataset expansion, checkpoint selection, local evaluation, inference tuning, and reproducible packaging.

AIMachine LearningText to SpeechEngineering
Fine-Tuning XTTS v2 for More Natural Bible Narration

The strongest checkpoint in this XTTS v2 fine-tune occurred at global step 10,725. Training continued to step 35,750, but evaluation loss increased and the later checkpoint produced weaker spectral and pitch measurements.

This article explains how I selected the model. It separates training signals from listening observations and documents the limits of the local evaluation.

Objective and baseline

Alveum created an internal challenge to develop an in-house voice for King James Version Bible narration. The target was the natural pacing and phrase-level melody of the existing ElevenLabs voice.

The supplied XTTS v2 baseline used 324 processed Genesis clips, or about 50 minutes of speech. It pronounced the text clearly, but its rhythm and pitch movement sounded robotic.

My main target was prosody: the timing, stress, and pitch pattern that gives a sentence its spoken shape. I evaluated intelligibility, pacing, pitch behavior, spectral similarity, and reproducibility.

Model and dataset decisions

I first tested Fish S2 because a newer architecture could have improved naturalness. In my listening tests, its early output did not exceed the 324-clip XTTS baseline, despite using more compute.

I stopped that experiment and returned to XTTS v2. Because I did not evaluate Fish S2 and XTTS with the same controlled pipeline, I treat this as an architecture decision rather than a quantitative benchmark.

XTTS also matched the project constraints. The baseline already provided compatible training code, inference code, dataset conventions, and a portable checkpoint format.

I expanded the processed dataset from 324 Genesis clips to 3,611 Bible and Gospel clips. The trainer used 3,575 clips for training and held out 36 clips for evaluation.

Data groupClips
Starter baseline324
Training set3,575
Evaluation set36
Total processed set3,611

The trainer configuration and log confirm these counts. Dataset size alone does not measure quality: transcript accuracy and clip boundaries also affect pronunciation, timing, and rhythm.

The run fine-tuned the XTTS v2 GPT component. The training log reports 441,018,563 parameters and one GPU.

Training settingValue
PrecisionFloat32
Batch size1
Epochs10
OptimizerAdamW
Learning rate5e-6
AdamW betas0.9, 0.96
Weight decay0.01
Checkpoint interval2,000 steps
Output sample rate24 kHz

Training behavior and checkpoint selection

At step 1,000, the logged sample loss was 2.4582, down from 4.0892 at step 0. These are individual training samples, not a stable evaluation series, so they only show that optimization was active.

At about the same stage, I heard less-flat pitch movement and better continuity between phrases. That is a listening observation; the training loss does not prove that prosody improved.

The held-out evaluation set provided a stronger checkpoint-selection signal. The Gospels run reached its minimum evaluation loss of 2.8185 at step 10,725, then worsened in every remaining epoch.

At the final step, 35,750, evaluation loss had increased to 3.0925. That value is 9.7% higher than the minimum, even though training had continued for seven more epochs.

The same pattern appeared in the smaller Genesis run. Its minimum occurred at step 5,652, before later checkpoints degraded.

Training evidenceEvaluation loss by global stepEvery point comes from the held-out evaluation pass at the end of an epoch.
Lower is better
3,611-clip Gospels runStep 10,725 · 2.8185Selected checkpoint · minimum evaluation loss
The Genesis and Gospels runs both reached their minimum evaluation loss before their final epoch. Global training step
Both runs selected an earlier checkpoint. Continued training increased held-out loss.
View the source data
324-clip Genesis run
StepLoss
1,8843.1101
3,7683.0551
5,652 · selected3.0367
7,5363.0746
9,4203.1275
11,3043.1283
13,1883.2043
15,0723.2562
16,9563.3401
18,8403.4393
20,7243.4656
22,6083.5642
24,4923.5975
26,3763.7815
28,2603.7737
3,611-clip Gospels run
StepLoss
3,5752.8874
7,1502.8227
10,725 · selected2.8185
14,3002.8321
17,8752.8420
21,4502.8668
25,0252.9122
28,6002.9699
32,1752.9970
35,7503.0925

I used best_model_10725.pth as the submitted model. I also compared four John 3:16 samples: the base model, an early fine-tune, the selected model, and checkpoint 34,000.

Listen for intelligibility, pause placement, pitch movement, and stability across the same sentence. Use headphones for the clearest comparison.

Playable waveform comparisonJohn 3:16 across four XTTS stagesPlay the same sentence at each stage, or select a position in any waveform to compare its timing.
Base XTTS modelSupplied 324-clip baseline
Decoding…WAV ↓
Earlier XTTS fine-tuneIntermediate model before selection
Decoding…WAV ↓
Selected Gospels modelValidation-best · step 10,725
Decoding…WAV ↓
Checkpoint 34,000Later model after validation loss increased
Decoding…WAV ↓
Waveforms are decoded from the published WAV files, trimmed at 40 dB below peak, and normalized per track. Shape shows timing and energy—not perceived quality.

No audio sample is playing.

Evaluation and inference tuning

I used duration, an approximate Mel Cepstral Distortion value, and F0 deviation to screen candidates. I then used listening tests for the final selection.

MCD estimates distance between spectral envelopes. F0 deviation compares pitch tracks in cents, where 100 cents equals one semitone. Lower values mean closer matches within this evaluation pipeline.

The MCD calculation was local and approximate. Its absolute values are not comparable with published benchmarks; only relative changes from the same script, reference, and test sentence are useful here.

On Genesis 1:1, checkpoint 34,000 was closer to the reference duration. The validation-best model had a 20.6% lower MCD value and a 7.0% lower F0 deviation.

Checkpoint comparison · Genesis 1:1Selected model versus checkpoint 34,000Switch metrics to see why the later checkpoint was not a uniform improvement.
Approximate Mel Cepstral DistortionValidation-best model is 20.6% lower
↓ Lower is better
Validation-best modelGlobal step 10,725
270.96best
Checkpoint 34,000Later training checkpoint
341.28
The validation-best model had 20.6% lower approximate MCD, indicating a closer spectral envelope within this local pipeline.
View all measured values
CandidateDuration errorApprox. MCDF0 deviation
Validation-best model+0.906 s270.96399.60 cents
Checkpoint 34,000+0.464 s341.28429.65 cents

Inference settings also changed the output. On Exodus 2:2, increasing speed from 1.00 to 1.25 reduced the duration error from 3.065 seconds to 0.337 seconds.

SpeedGenerated durationDifference from reference
1.0012.632 seconds+3.065 seconds
1.2010.774 seconds+1.207 seconds
1.259.903 seconds+0.337 seconds

I then swept temperature and top_p at speed 1.25. Temperature controls sampling randomness, while top_p limits sampling to the most probable token set whose cumulative probability reaches the threshold.

Temperature / top_pDuration differenceApprox. MCDF0 deviationPitch range
0.65 / 0.85+0.940 seconds291.85413.53910 cents
0.75 / 0.90-0.023 seconds309.15379.56940 cents
0.85 / 0.92-0.023 seconds310.08360.171,228 cents
0.95 / 0.95+0.046 seconds287.34341.65840 cents

The 0.85 / 0.92 setting produced the widest pitch range, but 0.95 / 0.95 had the lowest MCD and F0 deviation. I selected the latter as the better balance of similarity, pacing, and intelligibility.

The final inference defaults were:

speed = 1.25
temperature = 0.95
top_p = 0.95

Packaging and reproducibility

The selected checkpoint was approximately 5.6 GB, so I kept it outside Git. A reviewer places the following files in model/gospels_best/:

model.pth
config.json
vocab.json

The reviewer package includes a Python 3.11 environment, pinned dependencies, a local copy of TTS 0.22.0, CLI inference, a web interface, Docker configuration, and Windows and macOS instructions.

I tested the package in a new virtual environment with the same command provided to reviewers:

python scripts/inference.py \
  --text "In the beginning God created the heaven and the earth." \
  --reference reference_audio/reference.wav \
  --checkpoint model/gospels_best \
  --output output/reviewer_test.wav \
  --speed 1.25 \
  --temperature 0.95 \
  --top-p 0.95

The smoke test produced a 4.821-second, mono, 24 kHz PCM-16 WAV file. This verified that the model could load and generate audio outside the training environment.

Results and limitations

The selected XTTS model improved on the starter baseline in my listening tests and remained the strongest candidate by validation behavior and local checkpoint comparisons.

It did not reach the phrase-level melody of the ElevenLabs target. Punctuation improved pause placement but could not fully control pitch shape, and output quality remained sensitive to the reference clip.

The evaluation also has limits: only 36 clips were held out, the MCD implementation was approximate, no formal listening panel was used, and the official hidden test sentences were unavailable during packaging.

The next evaluation should use a larger held-out set and a blinded A/B listening test across short, medium, and long passages.

The submission later received first place in the Alveum AI Voice Model Challenge. More importantly, the evidence showed why the best result came from an earlier checkpoint and a tuned inference pipeline, not from more training alone.

Notes & responses