Kaldi Speech Decoding: Matching Features, Graphs, and Search Parameters
Configure Kaldi speech decoding by matching MFCC features to your acoustic model, preparing the data directory, and running standard decode scripts for GMM or chain models.
08 Nov 2025, 19:13 UTC

Decoding in Kaldi usually fails for mundane engineering reasons rather than model quality: the MFCC features were extracted with the wrong sample rate, the composed finite state transducer (FST) graph came from a different training run than the acoustic model, or the data directory is missing the speaker mappings the decoder expects. The practical takeaway is that a decoding run must start by confirming that the feature configuration matches the model's training metadata and that the graph and model directories share the same provenance.
This guide walks through preparing data, extracting features, configuring the Viterbi search, and running a standard Kaldi decoding recipe. It assumes you already have a trained model and a compiled graph, and focuses on the decisions that determine whether decoding succeeds.
Prerequisites
Kaldi's decoding architecture is modular. Before running any command, you need:
- Acoustic model directory: For a GMM-HMM model, this contains
final.mdl(the transition model and acoustic likelihoods) and possibly feature transforms such as LDA/MLLT matrices. For a chain TDNN model,final.mdlcontains the transition model and neural network weights. - Search graph directory: Contains
HCLG.fst(the composed transducer integrating HMM topology, lexicon, and language model) andwords.txt(the word symbol table). - Test audio: Mono WAV at the sample rate used during training—typically 16 kHz or 8 kHz. Kaldi does not resample automatically.
Prepare the data directory
The decoder reads utterance metadata from a data directory, not from raw audio paths. At minimum, create data/test/wav.scp and data/test/utt2spk:
mkdir -p data/test echo "utt001 /path/to/audio1.wav" > data/test/wav.scp echo "utt001 speakerA" > data/test/utt2spk # Add one line per utterance
Generate the reverse mapping:
utils/utt2spk_to_spk2utt.pl data/test/utt2spk > data/test/spk2utt
Expected check: data/test/spk2utt should exist and contain one speaker ID per line with the corresponding utterance IDs.
Step 1: Extract MFCC Features and CMVN
Kaldi decoders do not read waveforms; they read Mel-frequency cepstral coefficients (MFCCs). The extraction parameters must match the model's training configuration. A sample-rate mismatch is the most common cause of severe feature mismatch and high word error rate, and it produces no runtime warning.
Run the standard feature extraction script from the Kaldi root:
steps/make_mfcc.sh --nj 1 --cmd "$train_cmd" data/test exp/make_mfcc/test/log mfcc
Placeholders: $train_cmd is your queue or local run command (for example, utils/run.pl for local execution). The script writes data/test/feats.scp.
Next, compute cepstral mean and variance normalization (CMVN) statistics. These are stored in the data directory, not the model directory:
steps/compute_cmvn_stats.sh data/test exp/make_mfcc/test/log mfcc
In conf/mfcc.conf (or the config passed to the script), verify these values against the model's training metadata:
sample-frequency=16000 frame-length=25 frame-shift=10 low-freq=20 high-freq=7800 num-mel-bins=23
Expected check: data/test/feats.scp and data/test/cmvn.scp should exist and list every utterance ID.
Risk: If sample-frequency differs from the training rate, the decoder will still run, but the transcript will be unreliable.
Step 2: Configure the Decoder
Kaldi decoders read search parameters from a configuration file. Create conf/decode.config:
beam=13.0 max-active=7000 acoustic-scale=0.1 lattice-beam=6.0
Beam controls the width of the Viterbi search; larger values explore more hypotheses but increase memory and time. Max-active caps active states during search. Acoustic-scale weights acoustic versus language model scores.
There is no general-purpose Kaldi utility that verifies an HCLG.fst is compatible with a given final.mdl. The practical safeguard is to confirm that the graph directory and model directory were produced by the same training recipe and that words.txt matches the model's expected output labels. If the decoder aborts with a symbol-table or dimension error, the graph and model are from different builds.
Step 3: Execute Decoding
The exact decoding binary depends on the model type. Use the standard wrapper scripts, which handle feature I/O, CMVN application, and lattice extraction.
For a GMM-HMM model:
steps/decode.sh --nj 1 --cmd "$decode_cmd" \ exp/tri3a/graph data/test exp/tri3a/decode_test
For a chain TDNN model (nnet3):
steps/nnet3/decode.sh --nj 1 --cmd "$decode_cmd" \ exp/chain/tdnn/graph data/test exp/chain/tdnn/decode_test
Placeholders: exp/tri3a/graph is the directory containing HCLG.fst and words.txt. $decode_cmd is typically utils/run.pl for local runs. The wrappers call gmm-decode-faster or nnet3-latgen-faster internally, perform Viterbi search across the composed FST, and write lattices and best-path transcripts into the decode directory.
Risk: Large graphs built from large-vocabulary language models can require substantial RAM. If memory allocation fails, the decoder log will show an allocation error and the process will terminate.
Verifying the Result
A quick sanity check is to compare the output against a known test utterance with an expected word count. Inspect the transcript line in the decode directory:
utt001 THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
If the word count is wildly different, or the output is empty, revisit the feature configuration and confirm the graph and model share the same provenance. For quantitative evaluation, use Kaldi's scoring scripts to compute word error rate against a reference transcript.
| Output | Expected content |
|---|---|
decode_test/text | One transcript line per utterance ID |
decode_test/lat.*.gz | Compressed word lattices |
| Decoder log | No segment errors or memory allocation failures |
Limitations and Recovery
Kaldi's interfaces changed significantly between legacy 1.0.x GMM recipes and modern chain-based architectures. Scripts and directory layouts are not interchangeable; a decoding invocation written for a GMM model will not work with a chain TDNN model without modification. Always confirm that the recipe matches the model directory structure.
Decoding is read-only with respect to the model, but it writes new files. If a run fails or produces incorrect output, remove the generated state and start over:
rm -f data/test/feats.scp data/test/cmvn.scp rm -rf data/test/split* rm -rf exp/make_mfcc/test exp/tri3a/decode_test
After cleanup, correct the configuration—most often the sample rate or graph path—and re-run the steps above. Because the model files are never modified by decoding, no model rollback is required.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.