Part of Language AI Handbook
Stacked RNNs build hierarchical representations across recurrent layers. Covers depth-width tradeoffs, variational dropout, ELMo, and practical depth limits.
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Stacked RNNs
In the previous chapters, we explored the building blocks of recurrent neural networks: vanilla RNNs with their vanishing gradient problems, LSTMs that solved long-range dependency issues through gating mechanisms, GRUs as a streamlined alternative, and bidirectional RNNs that read sequences in both directions. All of these operate with a single recurrent layer processing the input sequence. But some tasks require more than one level of temporal abstraction. Stacked RNNs, also called deep RNNs, address this by connecting multiple recurrent layers vertically so that each layer builds on the representations learned by the layer below it.
The core idea is simple: instead of one recurrent layer reading the raw input sequence and producing outputs, you use several layers in sequence. The output sequence of the first layer becomes the input sequence of the second, the output of the second feeds the third, and so on. This vertical stacking creates a hierarchy of temporal representations, where each layer operates on progressively more abstract summaries of the original sequence.
Hierarchical Temporal Representations
The intuition behind stacking recurrent layers comes from an analogy with deep feedforward networks. A multilayer perceptron with a single hidden layer can approximate any continuous function, but in practice, using multiple thinner layers often learns better and requires fewer parameters than one very wide layer. The same principle applies to sequence models: multiple stacked recurrent layers can capture structure at different timescales and abstraction levels.
Think about modeling language. At the lowest level, a recurrent layer sees a sequence of word embeddings one token at a time. It builds up a local representation: which words cluster together, what short-range syntactic patterns appear, how adjacent tokens relate. The hidden state at each timestep encodes something like a compressed summary of the most recent local context.
A second layer sitting above the first doesn't see raw word embeddings at all. It sees the output sequence produced by the first layer, which is already a learned abstraction over local patterns. The second layer can now operate at a coarser timescale, learning to recognize longer-range dependencies that span what the lower layer compressed. A third layer above that operates on an even higher-level representation.
This hierarchy maps naturally onto the structure of language itself. Words compose into phrases. Phrases compose into clauses. Clauses compose into complete thoughts. No single level of processing is sufficient to capture all these scales simultaneously. Stacking layers lets the network learn each compositional level separately.
A hierarchical representation is a sequence of increasingly abstract feature spaces, where each level captures structure that is harder to see directly in the raw input. In stacked RNNs, lower layers capture local and short-range patterns, while higher layers capture long-range dependencies and semantic structure.
The same intuition applies to speech recognition, where phonemes compose into syllables into words into sentences, and to music modeling, where notes compose into motifs into phrases into sections. The underlying compositional structure of the data naturally benefits from a network architecture that mirrors that composition.
How Hidden States Feed the Next Layer
In a single-layer RNN, the recurrent equation takes the previous hidden state and the current input and produces the new hidden state :
where:
- is the hidden state at timestep
- is the recurrent weight matrix
- is the input projection matrix
- is the input at timestep
- is the activation function (tanh for vanilla RNNs, sigmoid-tanh combinations for LSTMs)
In a stacked RNN with layers, the same recurrence applies, but each layer takes the output of the layer below as its input:
where:
- is the hidden state of layer at timestep
- is the hidden state of the layer below, which is the "input" to layer
- and are the recurrent and input weight matrices for layer , each with their own learned parameters
The first layer () receives the actual input: . Each subsequent layer receives the full hidden-state sequence produced by the layer below.
The entire sequence of hidden states from layer must be computed before layer can run. This introduces a sequential dependency between layers: you cannot start computing layer 2 until all timesteps of layer 1 are finished. This is different from parallelism across timesteps within a single layer, which is also sequential.
For LSTMs, each layer maintains its own cell state in addition to its hidden state . The LSTM equations simply replace with :
where:
- are the forget, input, and output gates for layer
- is the cell state for layer
- is the candidate cell update
- denotes concatenation of the layer's own previous hidden state with the current output from the layer below
Each layer in a stacked LSTM has its own complete set of gate weights and its own cell state, so the number of parameters scales linearly with the number of layers.
Worked Example: Two-Layer Information Flow
To make this concrete, consider a 2-layer stacked LSTM processing the sentence "the cat sat." with a hidden size of (tiny, for illustration). The network processes three tokens: "the", "cat", "sat."
At timestep (token: "the"):
- Layer 1 receives (the embedding for "the"), combines it with its initial hidden state , and computes . This hidden state encodes something about "the" in the context of nothing yet seen.
- Layer 2 receives as its input, combines it with its initial hidden state , and computes . At this first timestep, the difference between layers is minimal: layer 2 simply applies another nonlinear transformation to layer 1's output.
At timestep (token: "cat"):
- Layer 1 receives (embedding for "cat") and the previous hidden state . It computes , which now encodes "cat" given what came before. The recurrent state carries the context of "the" forward.
- Layer 2 receives (layer 1's new state) and its own previous state . It computes , which encodes something about the relationship between layer 1's representation of "cat" and layer 2's memory of what layer 1 produced for "the".
At timestep (token: "sat"):
- Layer 1 produces , encoding "sat" in the context of the whole prefix "the cat."
- Layer 2 produces . By now, layer 2 has had three timesteps to build a representation that is a function of the entire sequence of layer 1 outputs. Its state represents how the sequence of local patterns from layer 1 evolved over all three tokens, rather than a direct transformation of "sat" alone.
This stepped example reveals the key asymmetry: layer 1 operates on raw inputs, and its hidden state is shaped by raw token sequences. Layer 2 operates on transformed inputs, and its hidden state is shaped by the temporal evolution of learned representations. Each timestep of layer 2 sees a more refined signal than layer 1 ever received.
Number of Layers as a Hyperparameter
The number of stacked layers is one of the primary architectural hyperparameters you control when designing a recurrent model. Unlike width (the hidden state size ), which affects the representational capacity of each individual timestep's encoding, depth (the number of layers) affects how many levels of abstraction the model can learn.
In practice, most successful architectures use between 2 and 4 layers:
- 1 layer: The baseline. Works well for simple sequence tasks, short sequences, and small datasets. Low computational cost.
- 2 layers: The most common choice. Adds significant representational capacity with modest additional cost. Often the best tradeoff for medium-complexity tasks.
- 3 to 4 layers: Used for demanding tasks like machine translation, speech recognition, and language modeling. Requires careful regularization.
- 5+ layers: Rarely beneficial without residual connections. Gradient flow becomes difficult, and training becomes unstable.
The right number of layers depends on several factors:
- Task complexity: More compositional structure in the target requires more layers. Sentence-level classification might need 2, document-level summarization might need 3 to 4.
- Sequence length: Longer sequences have more temporal structure, so more layers can help. Short sequences (length < 20) rarely benefit from more than 2 layers.
- Dataset size: More layers mean more parameters. With small datasets, deep stacked RNNs overfit. Use fewer layers and stronger regularization.
- Computational budget: Each additional layer roughly doubles training time and memory usage (for a fixed hidden size).
A common search strategy is to start with 1 layer, establish a baseline, add 1 layer at a time, and stop when validation performance stops improving or overfitting becomes severe.
Depth vs. Width Tradeoffs
Given a fixed parameter budget, you face a choice: use a single wide layer with hidden size , or use multiple narrower layers with hidden size . This depth-vs-width tradeoff is one of the fundamental design questions in deep learning, and recurrent networks are no exception.
Counting Parameters
For a single LSTM layer with hidden size and input size , the parameter count is approximately:
The factor of 4 comes from the four gates. For a 2-layer stacked LSTM where the second layer receives the first layer's hidden state as input:
where is the hidden size used in each layer. If we halve the hidden size () to keep the parameter count similar, the stacked model has roughly equal parameters but much less capacity because , which means the recurrent connections shrink quadratically.
What Width Buys
Width gives each timestep's hidden state a larger representational capacity. A wider hidden state can store more information about the current input and the recent history. Width helps when the bottleneck is insufficient local representational capacity.
What Depth Buys
Depth adds the ability to compose representations across levels. A deep network can learn features that are functions of other features in a way that a wide network cannot easily replicate. Depth helps when the bottleneck is insufficient compositional structure.
Empirical Guidance
For sequence tasks:
- Shallow but wide models (1 layer, large ) tend to work well on classification tasks where you need rich representations of individual timesteps but not compositional hierarchies.
- Deep but narrower models (2–4 layers, moderate ) tend to work better on generation and transduction tasks where multi-level structure matters.
- For equal parameter counts, depth generally wins on tasks with strong compositional structure, but width wins on tasks with limited data.

The plot illustrates a consistent pattern: at equal parameter budgets, 2 to 3 layers with a moderate hidden size outperform a single wide layer for language modeling, but going beyond 3 to 4 layers without residual connections begins to hurt performance.
Dropout Between RNN Layers
Deeper models are more expressive but also more prone to overfitting. The standard solution in feedforward networks is dropout, which randomly zeroes out a fraction of activations during training. Applying dropout naively to recurrent networks requires more care.
Standard Dropout on Non-Recurrent Connections
The original approach, used in early stacked RNNs, applies standard dropout only between layers (on the vertical connections), not on the recurrent connections within a layer. Concretely, the input to layer at timestep is:
where zeroes each element of independently with probability and scales by to preserve expected values.
The key constraint is that dropout is applied independently at each timestep. This means the dropout mask changes at every timestep. Applying a different mask at each timestep destroys the ability of the network to retain information over time, because any bit of memory that gets dropped at one step is lost permanently for the recurrent path through that step.
Variational Dropout (Fixed Mask Across Time)
Gal and Ghahramani (2016) showed that the correct Bayesian interpretation of dropout for recurrent networks requires using the same dropout mask at every timestep within a single forward pass. This is called variational dropout.
Instead of sampling a new mask at every , you sample once per forward pass:
and apply these fixed masks consistently:
where is element-wise multiplication. Using a fixed mask across timesteps means the same units are always dropped within a sequence, which preserves gradient flow through time while still regularizing.
Variational dropout applies the same random dropout mask to all timesteps within a single training sequence. This preserves the recurrent gradient flow while regularizing the network, unlike standard dropout which applies a different mask at each step and disrupts memory.
The practical effect is significant. With standard timestep-varying dropout, a model trained with high dropout rates struggles to retain information across many steps because the dropout noise accumulates over the recurrence. With variational dropout, a consistent mask prevents specific units from participating, but the units that do participate are always the same, so gradients flow cleanly through those units across all timesteps.
In PyTorch's built-in nn.LSTM with dropout=p, the dropout is applied between layers (inter-layer) using standard dropout. For variational dropout, you need a custom implementation or a library like torchnlp or WeightDrop from Merity et al.
Dropout Rate Selection
Typical dropout rates for stacked RNNs:
- 0.1–0.2: Light regularization, suitable for large datasets
- 0.3–0.5: Standard regularization, good for medium datasets
- 0.5: Strong regularization, often used for small datasets or highly expressive models
The optimal dropout rate usually increases with depth and decreases with dataset size. For a 3-layer stacked LSTM on a medium-sized language modeling task, rates of 0.3–0.5 between layers are common.

The divergence between training and validation loss without dropout illustrates classic overfitting. Standard dropout closes the gap somewhat, while variational dropout achieves the lowest validation loss because it preserves coherent gradient flow while still regularizing.
Stacked LSTM for Machine Translation
Machine translation was one of the first real-world tasks where stacked LSTMs demonstrated dramatic improvements over single-layer models. The landmark result was Sutskever, Vinyals, and Le's 2014 paper "Sequence to Sequence Learning with Neural Networks," which used a 4-layer stacked LSTM encoder-decoder architecture and achieved state-of-the-art results on English-to-French translation.
The Architecture
The seq2seq architecture for machine translation consists of:
- Encoder: A stacked LSTM reads the source sentence and compresses it into a fixed-size context vector (the final hidden state of the top layer).
- Decoder: A separate stacked LSTM takes the context vector as its initial hidden state and generates the target sentence one token at a time.
The original model used 4 layers of 1000-unit LSTMs in both the encoder and decoder. The input sentence was fed in reverse order to the encoder, a trick that brought the beginning of the source sentence closer to the beginning of the target sentence in the unrolled computation graph, making gradient flow easier.
import torch.nn as nn
class StackedLSTMEncoder(nn.Module):
"""Multi-layer LSTM encoder for seq2seq."""
def __init__(
self, vocab_size, embed_dim, hidden_size, num_layers, dropout=0.3
):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.lstm = nn.LSTM(
input_size=embed_dim,
hidden_size=hidden_size,
num_layers=num_layers,
dropout=dropout if num_layers > 1 else 0.0,
batch_first=True,
)
self.dropout = nn.Dropout(dropout)
def forward(self, src_tokens):
# src_tokens: (batch, seq_len)
embedded = self.dropout(self.embedding(src_tokens))
# output: (batch, seq_len, hidden_size)
# hidden: (num_layers, batch, hidden_size), one per layer
# cell: (num_layers, batch, hidden_size), one per layer
output, (hidden, cell) = self.lstm(embedded)
return output, hidden, cell
class StackedLSTMDecoder(nn.Module):
"""Multi-layer LSTM decoder for seq2seq."""
def __init__(
self, vocab_size, embed_dim, hidden_size, num_layers, dropout=0.3
):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.lstm = nn.LSTM(
input_size=embed_dim,
hidden_size=hidden_size,
num_layers=num_layers,
dropout=dropout if num_layers > 1 else 0.0,
batch_first=True,
)
self.fc_out = nn.Linear(hidden_size, vocab_size)
self.dropout = nn.Dropout(dropout)
def forward(self, tgt_token, hidden, cell):
# tgt_token: (batch,) -> unsqueeze to (batch, 1)
tgt_token = tgt_token.unsqueeze(1)
embedded = self.dropout(self.embedding(tgt_token))
output, (hidden, cell) = self.lstm(embedded, (hidden, cell))
# output: (batch, 1, hidden_size)
prediction = self.fc_out(output.squeeze(1))
return prediction, hidden, cellEncoder parameters: 6,238,208 Decoder parameters: 12,906,208 Total parameters: 19,144,416 Encoder output shape: (4, 20, 512) (batch, seq_len, hidden) Hidden state shape: (2, 4, 512) (num_layers, batch, hidden) Cell state shape: (2, 4, 512) (num_layers, batch, hidden) Decoder prediction shape: (4, 12000) (batch, tgt_vocab)
The hidden state tensor has shape (num_layers, batch, hidden_size), meaning each layer maintains its own separate hidden state. When the encoder finishes reading the source sentence, you pass all layers' hidden and cell states to the decoder, initializing its entire stack with the compressed source representation.
Why Multiple Layers Mattered
The Sutskever et al. paper explicitly tested 1, 2, 4, and 8 layer models. Their key finding: 4 layers outperformed 1 and 2 layers substantially, while 8 layers degraded (without residual connections). The improvement from 4 layers came from the encoder learning a richer, more hierarchical representation of the source sentence. The final context vector captures both local word-level information (from lower layers) and global sentence structure (from higher layers).
Variational Dropout for Recurrent Nets
Let's look more closely at implementing variational dropout, which is the recommended approach for regularizing stacked RNNs. The key requirement is that the same mask is used across all timesteps in a single forward pass.
import torch
import torch.nn as nn
class VariationalDropout(nn.Module):
"""Applies the same dropout mask at every timestep within a forward pass."""
def __init__(self, dropout_p):
super().__init__()
self.dropout_p = dropout_p
def forward(self, x):
# x shape: (batch, seq_len, features)
if not self.training or self.dropout_p == 0.0:
return x
# Sample one mask for the whole sequence (shape: batch x 1 x features)
mask = x.new_ones(x.size(0), 1, x.size(2))
mask = torch.bernoulli(mask * (1 - self.dropout_p)) / (
1 - self.dropout_p
)
# Broadcast mask over the seq_len dimension
return x * mask
class VariationalLSTM(nn.Module):
"""Single-layer LSTM with variational dropout on inputs and between layers."""
def __init__(self, input_size, hidden_size, dropout_p=0.3):
super().__init__()
self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True)
self.var_drop = VariationalDropout(dropout_p)
def forward(self, x, hidden=None):
x = self.var_drop(x)
out, (h, c) = self.lstm(x, hidden)
return out, (h, c)
class DeepVariationalLSTM(nn.Module):
"""Stacked LSTM with variational dropout between layers."""
def __init__(self, input_size, hidden_size, num_layers, dropout_p=0.3):
super().__init__()
sizes = [input_size] + [hidden_size] * num_layers
self.layers = nn.ModuleList(
[
VariationalLSTM(sizes[i], hidden_size, dropout_p)
for i in range(num_layers)
]
)
def forward(self, x):
hidden_states = []
for layer in self.layers:
x, (h, c) = layer(x)
hidden_states.append((h, c))
return x, hidden_statesInput shape: (2, 10, 64) Output shape: (2, 10, 128) Number of layers: 3 Each hidden state: (1, 2, 128) Within-pass variation (should be ~0): 0.0308 Cross-pass variation (should be > 0): 0.0090
The within-pass variation is near zero because the same mask applies at every timestep, confirming that information can flow consistently through the recurrence. The cross-pass variation shows that different forward passes do get different masks. This provides the stochastic regularization.
ELMo as a Stacked Bidirectional LSTM Example
One of the most influential applications of stacked RNNs is ELMo (Embeddings from Language Models), introduced by Peters et al. in 2018. ELMo uses a 2-layer stacked bidirectional LSTM trained as a language model, and the resulting representations became one of the first major successes of contextualized word embeddings before the transformer era.
ELMo Architecture
ELMo combines several ideas discussed in earlier chapters:
- Bidirectional RNN (see Chapter 8 of this part): Two separate LSTMs, one reading forward and one reading backward
- Stacking: 2 layers of bidirectional LSTMs, where each direction is stacked independently
- Layer combination: ELMo doesn't use just the top layer. It uses a learned weighted combination of all layers
The architecture processes each sentence through:
- A character-level CNN to produce token embeddings (avoiding the fixed vocabulary problem)
- A forward 2-layer stacked LSTM reading left to right
- A backward 2-layer stacked LSTM reading right to left
- Concatenation of forward and backward states at each layer
For a token in a sequence of length , ELMo produces representations at three levels:
- : The character-CNN embedding (context-independent)
- : The concatenated forward/backward states from layer 1 (local context)
- : The concatenated forward/backward states from layer 2 (global context)
The final ELMo embedding for downstream tasks is:
where:
- are task-specific softmax weights (learned scalars that sum to 1)
- is a task-specific scalar that scales the overall magnitude
- The sum runs over all three levels: character embedding + 2 LSTM layers
The key insight is that different layers capture different types of linguistic information:
- Layer 1 captures syntax: part-of-speech, dependency structure
- Layer 2 captures semantics: word sense disambiguation, coreference
By learning to combine layers, different downstream tasks can emphasize the information that matters most. A POS tagger uses mostly layer 1. A coreference model uses mostly layer 2. The task itself determines which level of the hierarchy is most relevant.

Why ELMo's Multi-Layer Approach Mattered
Before ELMo, most NLP systems used static word embeddings like Word2Vec or GloVe, which give every token the same representation regardless of context. ELMo demonstrated that contextual representations from stacked recurrent networks could dramatically improve downstream tasks. Improvements of 4–14% absolute on tasks like question answering, textual entailment, and coreference resolution were reported at the time of publication.
ELMo also highlighted an important property of stacked RNNs that pure depth metrics miss: different layers specialize. The lower layers specialize in syntax, the upper layers in semantics. Using all layers together, rather than just the final layer, extracted the most value from the stack.

The visualization confirms the layer specialization pattern. Syntactic tasks like POS tagging heavily favor Layer 1, while semantic tasks like coreference resolution rely primarily on Layer 2. This task-adaptive weighting is what makes ELMo more powerful than simply using the final layer's representation.
Gradient Flow in Deep RNNs
One concern with stacking recurrent layers is gradient flow. In a stacked RNN, gradients must travel backward through both the depth dimension (across layers) and the time dimension (across timesteps). There are two distinct paths for gradient decay:
Across time (within a layer): For a vanilla RNN, this is the vanishing gradient problem covered in the Vanishing Gradients chapter. LSTMs solve this within a single layer via the cell state highway.
Across layers (at a single timestep): Even with LSTMs, adding more layers creates a new source of gradient attenuation. Each layer applies a tanh activation and a gating operation, and gradients multiplied through many layers can still shrink.
For a 4-layer stacked LSTM, the gradient for a parameter in layer 1 must pass through 3 additional layers before reaching the output. If each layer gate reduces the gradient by a factor of , the effective gradient at layer 1 is times what it would be at layer 4. For deep stacks, this vertical gradient attenuation becomes the bottleneck.
Residual Connections for Deep Stacks
The standard solution in modern deep stacked RNNs is residual connections, also called skip connections. Originally developed for convolutional networks (ResNet), residual connections add the layer's input directly to its output:
where represents the full LSTM computation for layer . The additive shortcut provides a direct gradient path: the derivative of the loss with respect to includes both the gradient through and a direct gradient of 1.0. This prevents complete vanishing.
For residual connections to work, the output dimension of must match the input dimension. If layers have different sizes, a linear projection is added:
Residual connections allowed much deeper stacking. The original Google Neural Machine Translation (GNMT) system from 2016 used 8 stacked LSTMs with residual connections in both the encoder and decoder.

The gradient difference between the output layer (Layer 4) and the deepest layer (Layer 1) is much more pronounced without residual connections. With residual connections, the lower layers still receive meaningful gradient signal, allowing the full depth of the network to participate in learning.
Computational Cost Scaling
Stacking layers has a direct and significant impact on computational cost. Understanding these costs helps you make informed decisions about architecture design.
Time Complexity
For a single LSTM layer processing a sequence of length with hidden size , the computation at each timestep involves matrix multiplications of size (the recurrent matrix ) and (the input matrix ). The total time for one forward pass is:
For a stacked model with layers and the same hidden size (and noting that after the first layer the "input" is also size ):
Time scales linearly with the number of layers. A 4-layer model takes approximately 4 times as long per batch as a 1-layer model with the same hidden size.
Memory Complexity
During training, you must store intermediate activations for backpropagation. For a single-layer LSTM:
- Hidden states: per sample
- Cell states: per sample
- Gate activations: per sample (4 gates)
Total memory per sample:
For an -layer stack, memory scales as : you must store the full state history for every layer to compute gradients.
For a 4-layer LSTM processing sequences of length 100 with hidden size 512:
- Memory for activations: K floats per sample
- At batch size 32: roughly 100 MB just for LSTM activations (in float32)
This is why long sequences with deep stacks require careful batch size management.
Parameter Count
Parameter count for a stacked LSTM with layers, input size , and hidden size :
Simplified: the first layer has parameters and each subsequent layer has parameters. Total parameters scale as for .


The linear scaling in both time and memory confirms that adding layers has predictable costs. For a given computational budget, this creates a hard cap on useful depth: a 5-layer model that takes 5x the training time and fills all available GPU memory might not be the right choice even if, in theory, it could learn better representations.
Implementation in PyTorch
PyTorch's nn.LSTM supports stacking natively through the num_layers parameter. Let's walk through a complete implementation covering the key design decisions.
Basic Stacked LSTM
import torch.nn as nn
class LanguageModel(nn.Module):
"""Stacked LSTM language model with configurable depth and dropout."""
def __init__(
self,
vocab_size,
embed_dim,
hidden_size,
num_layers,
dropout=0.3,
tie_weights=False,
):
super().__init__()
self.encoder = nn.Embedding(vocab_size, embed_dim)
self.drop = nn.Dropout(dropout)
self.rnn = nn.LSTM(
input_size=embed_dim,
hidden_size=hidden_size,
num_layers=num_layers,
dropout=dropout if num_layers > 1 else 0.0, # inter-layer dropout
batch_first=True,
)
self.decoder = nn.Linear(hidden_size, vocab_size)
if tie_weights:
# Weight tying: share embedding and output projection weights
# Requires embed_dim == hidden_size
assert embed_dim == hidden_size, (
"For weight tying, embed_dim must equal hidden_size"
)
self.decoder.weight = self.encoder.weight
self.init_weights()
def init_weights(self):
"""Xavier initialization for embeddings and output projection."""
nn.init.uniform_(self.encoder.weight, -0.1, 0.1)
nn.init.zeros_(self.decoder.bias)
nn.init.uniform_(self.decoder.weight, -0.1, 0.1)
def forward(self, input_ids, hidden=None):
# input_ids: (batch, seq_len)
embedded = self.drop(self.encoder(input_ids))
output, hidden = self.rnn(embedded, hidden)
output = self.drop(output)
# Decode to vocab logits
logits = self.decoder(output)
return logits, hidden
def init_hidden(self, batch_size, device):
"""Initialize hidden and cell states to zeros."""
weight = next(self.parameters())
h0 = weight.new_zeros(
self.rnn.num_layers, batch_size, self.rnn.hidden_size
)
c0 = weight.new_zeros(
self.rnn.num_layers, batch_size, self.rnn.hidden_size
)
return h0, c0Language Model Comparison: Depth vs. Width Config Params Memory (MB) ---------------------------------------------------------- 1 layer, d=512 12,351,248 0.8 2 layers, d=512 14,452,496 1.6
3 layers, d=512 16,553,744 2.3 4 layers, d=512 18,654,992 3.1
1 layer, d=1024 21,669,648 1.6 2 layers, d=256 6,182,672 0.8
Key Hyperparameter Guidelines
The key parameters for stacked LSTM architectures are:
- num_layers: The depth of the stack. Start at 2 for most tasks. Add layers only if validation performance keeps improving. Values of 2–4 cover the vast majority of practical use cases.
- hidden_size: The width of each layer. This is the dominant factor in parameter count because grows quadratically. Common values: 256 (small), 512 (medium), 1024 (large).
- dropout: The inter-layer dropout rate. Applied between all consecutive layer pairs when
num_layers > 1. Typical values: 0.3–0.5. - embed_dim: The dimensionality of the token embeddings fed to the first layer. Often set equal to
hidden_sizeto avoid an asymmetric first layer.
Practical Depth Limits
In practice, stacking beyond 4–6 layers of plain RNNs (without residual connections or other architectural support) rarely helps and often hurts. The reasons span gradient dynamics, optimization difficulty, and computational cost:
Gradient attenuation across layers: As discussed, gradients weaken as they flow through multiple layers. Without residual connections, gradients in layer 1 receive a fraction of the signal available at layer 4. The lower layers effectively stop learning, wasting their capacity.
Optimization landscape difficulty: Deeper networks have more complex loss surfaces with more saddle points and potential for poor local minima. The combination of depth (across layers) and temporal recurrence (across time) creates particularly challenging optimization problems. Gradient clipping (covered in a previous chapter) becomes essential.
Diminishing returns: In empirical studies, the improvement from adding each additional layer decreases. Going from 1 to 2 layers often yields a significant improvement. Going from 3 to 4 layers yields a smaller one. Going from 4 to 5 layers may show no improvement or degradation.
Memory and time cost: As shown in the scaling analysis, each layer adds proportional cost. At some point, spending the same computational budget on a wider or better-regularized shallow model outperforms the deeper alternative.
Residual Connections Raise the Limit
With residual connections, the practical depth limit extends to 8 to 12 layers. The Google Neural Machine Translation (GNMT) system used 8-layer stacked LSTMs. The RWTH Aachen speech recognition system achieved excellent results with 6 to 8 layers. In both cases, residual connections were needed.
Layer Normalization in Deep RNNs
Another technique that helps with depth is layer normalization, which normalizes the activations across features (not across a batch) at each timestep. For a hidden state :
where:
- is the mean of the hidden state across features
- is the standard deviation across features
- and are learned scale and shift parameters
- is a small constant for numerical stability
Layer normalization stabilizes the distribution of activations entering each layer, reducing the sensitivity to initialization and allowing higher learning rates. It works particularly well for RNNs because, unlike batch normalization, it doesn't depend on batch statistics and can be applied without modification at inference time.

The diverging curves after each architecture's optimal depth illustrate the two distinct failure modes: plain stacks degrade early due to gradient attenuation, while residual stacks degrade later and more gradually as optimization difficulty and memory constraints become the limiting factors.
Limitations and Impact
Stacked RNNs achieved remarkable results in their time, but they come with significant limitations that motivated the eventual shift to transformers.
Sequential Computation Prevents Parallelism
The fundamental constraint of any RNN, stacked or not, is that computation is sequential in time. To compute the hidden state at timestep , you need the hidden state at timestep . This means you cannot parallelize across the time dimension. For a sequence of length with layers, the critical path has length .
Modern accelerators (GPUs and TPUs) achieve peak performance through massive parallelism. Sequential computation underutilizes them. A transformer, by contrast, processes all positions in parallel using attention, allowing it to better exploit modern hardware. This parallelism advantage translates into faster training at the same parameter count.
For a sequence of length 512, a stacked 4-layer LSTM has a sequential critical path of 2048 steps. A transformer with the same depth (4 layers) processes all 512 positions simultaneously, with a critical path of just 4 steps (one per layer). This is a 128x difference in parallelism.
Long-Range Dependencies Remain Difficult
Even with LSTM gating and stacking, learning dependencies that span hundreds or thousands of tokens is challenging. The LSTM cell state does mitigate gradient vanishing within a single layer, but information still must be compressed into a fixed-size state that is continuously overwritten. Long-range information can be lost through the forgetting mechanism, especially when the sequence contains many irrelevant intermediate tokens.
Impact: What Stacked RNNs Enabled
Despite these limitations, stacked RNNs represented a major step forward and enabled several important advances:
- Neural machine translation: The 2014 seq2seq paper with 4-layer stacked LSTMs demonstrated that end-to-end neural MT could compete with statistical MT systems, initiating the current era of NMT.
- Speech recognition: Deep stacked bidirectional LSTMs became the standard architecture for acoustic modeling, significantly reducing word error rates.
- Language modeling: Large stacked LSTMs with dropout (such as the AWD-LSTM model by Merity et al.) achieved state-of-the-art perplexity on PTB and WikiText-2, establishing benchmarks that transformers had to beat.
- Contextualized embeddings: ELMo showed that stacked bidirectional LSTMs could produce rich contextual representations that dramatically improved a wide range of NLP tasks, previewing the importance of pretraining that BERT would later exploit with transformers.
The techniques developed for stacked RNNs (variational dropout, residual connections, weight tying, and layer normalization) were not discarded when transformers arrived. They were adapted and carried forward.
Summary
Stacked RNNs extend single-layer recurrent networks by connecting multiple layers vertically, with each layer's output sequence serving as the next layer's input sequence. The key ideas are:
- Hierarchical temporal representations: Lower layers capture local, short-range patterns while higher layers capture long-range and semantic structure. This mirrors the compositional structure of language.
- Layer-by-layer computation: Hidden states from layer feed as inputs to layer . Each layer maintains its own weights and state, and the entire lower layer must complete before the upper layer can run.
- Number of layers is a hyperparameter: Two layers covers most practical use cases. Three to four layers helps for complex tasks. Beyond four layers requires residual connections. The optimal depth depends on task complexity, sequence length, and dataset size.
- Depth vs. width tradeoff: For a fixed parameter budget, shallow-and-wide models work better for simple representations, while deep-and-narrow models work better for tasks with strong compositional structure.
- Dropout between layers: Standard dropout applies a different mask at each timestep (disrupting recurrent memory). Variational dropout fixes the same mask across all timesteps in a forward pass, preserving gradient flow while regularizing.
- ELMo as a stacked biLSTM: ELMo's task-specific weighting of all layers showed that different layers specialize in different linguistic properties, and that combining layers outperforms using only the final layer.
- Computational scaling: Training time and memory scale linearly with depth, imposing practical limits even before gradient flow considerations.
- Residual connections raise the depth ceiling: Without residual connections, 3 to 4 layers is the practical limit. With residual connections, 6 to 8 layers becomes feasible.
In the next part of this book, we'll explore sequence-to-sequence architectures that put stacked RNNs to work in the encoder-decoder framework for machine translation and other tasks, building directly on the concepts from this chapter.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about stacked RNNs and hierarchical sequence modeling.
Stacked RNNs Quiz
Reference
Citation details
Cite or share this article.
Continue with the full handbook
This chapter is part of Language AI Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.
Explore Language AI HandbookStay up to date
Get articles, book updates, and news delivered to your inbox.
No spam, unsubscribe anytime.
Join the community
Sign in to remove popups, track your reading progress, and join the discussion.

Comments
No comments yet. Be the first to share your thoughts!