Skip to content

fix: only unsqueeze hidden state for single-layer unidirectional decoder - #46

Open
Mukller wants to merge 1 commit into
timbmg:masterfrom
Mukller:fix/bidirectional-hidden-state-shape
Open

fix: only unsqueeze hidden state for single-layer unidirectional decoder#46
Mukller wants to merge 1 commit into
timbmg:masterfrom
Mukller:fix/bidirectional-hidden-state-shape

Conversation

@Mukller

@Mukller Mukller commented Jul 9, 2026

Copy link
Copy Markdown

Bug Fix: Incorrect hidden state shape in inference() for bidirectional/multi-layer RNN

When using a bidirectional or multi-layer decoder, inference() already reshapes
the hidden state via .view(hidden_factor, batch_size, hidden_size) to produce
the correct (num_layers * num_directions, batch, hidden_size) tensor.

However, .unsqueeze(0) is then applied unconditionally, adding an extra
leading dimension and producing the wrong shape (1, hidden_factor, batch, hidden_size).

GRU/LSTM expects (num_layers * num_directions, batch, hidden_size) — the extra
dimension causes a RuntimeError: Expected hidden size (2, N, H), got [1, 2, N, H].

Before (wrong — unsqueeze applied regardless of bidirectional/num_layers):

if self.bidirectional or self.num_layers > 1:
    hidden = hidden.view(self.hidden_factor, batch_size, self.hidden_size)

hidden = hidden.unsqueeze(0)   # ❌ creates (1, factor, batch, hidden)

After (fixed — unsqueeze only for simple single-layer unidirectional case):

if self.bidirectional or self.num_layers > 1:
    hidden = hidden.view(self.hidden_factor, batch_size, self.hidden_size)
else:
    hidden = hidden.unsqueeze(0)   # ✓ only adds dim when needed

Closes #35

@Mukller Mukller left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: fix unsqueeze for single-layer unidirectional decoder

Summary

Fixes an unconditional hidden.unsqueeze(0) call that was applied even when the decoder is bidirectional or has multiple layers — cases where hidden has already been reshaped to (hidden_factor, batch_size, hidden_size) by the preceding view() call. Applying unsqueeze(0) after the reshape would produce an incorrect 4D tensor, causing a dimension mismatch in the LSTM decoder.

Critical Issues

# File Line Issue Severity
1 model.py ~130 hidden.unsqueeze(0) was called unconditionally, adding an extra dimension even when bidirectional/multi-layer hidden state was already reshaped 🔴 Critical

What the Fix Does

# Before (bug)
if self.bidirectional or self.num_layers > 1:
    hidden = hidden.view(self.hidden_factor, batch_size, self.hidden_size)
hidden = hidden.unsqueeze(0)  # always runs — wrong for bidi/multi-layer

# After (correct)
if self.bidirectional or self.num_layers > 1:
    hidden = hidden.view(self.hidden_factor, batch_size, self.hidden_size)
else:
    hidden = hidden.unsqueeze(0)  # only for single-layer unidirectional

What Looks Good

  • Minimal fix with no extraneous changes
  • The else branch is the correct guard — the view() already handles the multi-layer/bidi case, and unsqueeze(0) is only needed for the simple single-layer decoder to add the num_layers=1 dimension expected by nn.LSTM

Verdict

Request Changes → now fixed. This bug would silently produce wrong results or runtime errors when using bidirectional or stacked LSTM decoders.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bi-directional gru

1 participant