LSTM Gate Equations › The Combined Weight Matrix Formulation
Language AI HandbookAll chapters LSTM Gate Equations › The Combined Weight Matrix FormulationOne section · Sections in this chapter13 / 17 ↓
Reading tools Definitions & inline links
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
In practice, frameworks like PyTorch don't compute each gate separately. Instead, they concatenate all weight matrices and compute all gates in a single matrix multiplication, which is more efficient on GPUs.
The combined formulation stacks the four weight matrices vertically into single large matrices:
where:
- : combined input weight matrix, stacking all four gate input weights
- : combined recurrent weight matrix, stacking all four gate hidden weights
- : combined bias vector, concatenating all four gate biases
- The subscripts , , , denote input gate, forget gate, candidate, and output gate respectively
Then all gates are computed in one step:
The notation indicates that different activation functions are applied to different portions of the result: sigmoid to the first elements (input gate), sigmoid to the next elements (forget gate), tanh to the next elements (candidate), and sigmoid to the final elements (output gate).
This produces a -dimensional vector that is split into four chunks of size each. The efficiency gain comes from performing two large matrix multiplications instead of eight smaller ones.
class EfficientLSTMCell:
"""LSTM cell using combined weight matrices for efficiency."""
def __init__(self, input_dim, hidden_dim):
self.input_dim = input_dim
self.hidden_dim = hidden_dim
# Combined weight matrices
scale_x = np.sqrt(2.0 / (input_dim + hidden_dim))
scale_h = np.sqrt(2.0 / (hidden_dim + hidden_dim))
self.W = np.random.randn(4 * hidden_dim, input_dim) * scale_x
self.U = np.random.randn(4 * hidden_dim, hidden_dim) * scale_h
self.b = np.zeros(4 * hidden_dim)
# Initialize forget gate bias to 1
self.b[hidden_dim : 2 * hidden_dim] = 1.0
def forward(self, x_t, h_prev, c_prev):
"""Forward pass using combined matrices."""
h = self.hidden_dim
# Single matrix multiplication for all gates
gates = self.W @ x_t + self.U @ h_prev + self.b
# Split and apply activations
i_t = sigmoid(gates[0:h])
f_t = sigmoid(gates[h : 2 * h])
c_tilde = tanh(gates[2 * h : 3 * h])
o_t = sigmoid(gates[3 * h : 4 * h])
# Cell state and hidden state updates
c_t = f_t * c_prev + i_t * c_tilde
h_t = o_t * tanh(c_t)
return h_t, c_tLet's compare the performance of both implementations:
import time
efficient_lstm = EfficientLSTMCell(input_dim=100, hidden_dim=256)
standard_lstm = LSTMCell(input_dim=100, hidden_dim=256)
x = np.random.randn(100)
h = np.zeros(256)
c = np.zeros(256)
# Time standard implementation
start = time.time()
for _ in range(1000):
standard_lstm.forward(x, h, c)
standard_time = time.time() - start
# Time efficient implementation
start = time.time()
for _ in range(1000):
efficient_lstm.forward(x, h, c)
efficient_time = time.time() - start
speedup = standard_time / efficient_timePerformance comparison (1000 forward passes): Standard implementation: 93.8 ms Efficient implementation: 71.2 ms Speedup: 1.32x
The efficient implementation is faster because it performs fewer matrix multiplications. The standard implementation does 8 matrix multiplications (2 per gate), while the efficient version does only 2 (one for inputs, one for hidden states). On GPUs, this difference is even more pronounced because large matrix multiplications are highly optimized.