Taming the HCLG Graph: Balancing Grammar Size and Decoding Speed in Kaldi
Learn how to balance the G (Grammar) component of Kaldi's HCLG graph to optimize the trade‑off between Word Error Rate (WER) and memory consumption during decoding.
08 Mar 2026, 13:16 UTC

The Memory Wall in Speech Decoding
When building a speech recognition system in Kaldi, you eventually hit a wall where adding a more complex language model (LM) no longer improves accuracy but instead crashes your decoder or slows it to a crawl. This happens because the HCLG graph—the Weighted Finite State Transducer (WFST) that guides the decoder—expands exponentially as the grammar grows.
The takeaway is simple: the size of your G (Grammar) transducer is the primary lever for controlling the trade‑off between Word Error Rate (WER) and the Real‑Time Factor (RTF). If your graph is too large to fit in RAM, the system will swap to disk or crash; if it is too pruned, you lose the linguistic context needed to disambiguate similar‑sounding words.
Understanding the HCLG Pipeline
The HCLG graph is a composition of four distinct transducers. To understand where the bottlenecks occur, you have to look at how they merge:
- H (HMM): Maps HMM states to context‑dependent phones.
- C (Context): Maps context‑dependent phones to mono‑phones.
- L (Lexicon): Maps sequences of phones to words.
- G (Grammar): Maps sequences of words to probabilities based on an N‑gram model.
The critical operation is the composition of L and G. When Kaldi merges these, it creates a massive state machine where every possible word sequence allowed by the grammar is mapped to its phonetic realization. To keep this manageable, Kaldi uses determinization (removing redundant paths to the same state) and weight pushing (moving probabilities as far forward in the graph as possible). Weight pushing is essential because it allows the beam search to prune unlikely paths earlier, reducing the number of active states the CPU must track.
Worked Example: Building the Graph from a KenLM Model
Most practitioners use KenLM for the G component because of its memory efficiency. To integrate a binary ARPA language model into your Kaldi graph, you typically use the utils/compose_lm.sh script. This script handles the conversion of the LM into a WFST and composes it with the lexicon.
Run the following from your sri directory (assuming you have already built your L.fst and C.fst):
# Run as a user with write permissions to the sri/ directory
# Replace 'lm.bin' with your KenLM binary file
utils/compose_lm.sh lm.bin L.fst G.fst
Expected Check: After running this, check the size of G.fst. If the file size is several gigabytes, you may need to apply pruning to the LM before composition. You can verify the graph's integrity by checking the script output for \"unreachable states\" warnings, which indicate that parts of your lexicon are not supported by your grammar.
The N‑gram Trade‑off: 3‑gram vs. 4‑gram
Choosing the order of your N‑gram model is a direct decision about memory vs. accuracy. A 4‑gram model captures longer dependencies (e.g., \"The United States of America\") more accurately than a 3‑gram model, but it increases the number of states in the G transducer significantly.
| Metric | 3‑gram G Transducer | 4‑gram G Transducer |
|---|---|---|
| Graph Size | Moderate | Large to Very Large |
| RAM Usage | Fits on most workstations | May require high‑memory servers |
| WER | Baseline | Generally lower (better) |
| RTF | Faster decoding | Slower due to larger search space |
Limitations and Risks
The most dangerous part of HCLG construction is determinization failure. If your grammar contains complex cycles or an extremely large vocabulary, the determinization process can hang or run out of memory. This is because the algorithm attempts to create a unique path for every possible input sequence.
Additionally, if your lexicon (L) contains phone sets that aren't properly mapped in the H or C transducers, you will create \"dead ends\" in the graph. The decoder will simply fail to recognize those words, regardless of how strong the language model probability is.
Verifying the Result
To ensure your graph is performing optimally, do not rely on a single test file. Use a representative subset of your data and monitor the decode tool's memory footprint. If you notice a spike in memory usage that leads to system instability, you have two options:
- Prune the LM: Use KenLM to remove low‑probability N‑grams before composing the graph.
- Lattice Rescoring: Build a smaller HCLG graph for the first pass to generate a lattice, then use a much larger, separate LM to rescore that lattice in a second pass. This provides the accuracy of a 4‑gram (or larger) model without the memory overhead of a massive static graph.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.