Skip to content

Fix LSTM real-time safety (#218)#299

Open
rhaist wants to merge 1 commit into
sdatkinson:mainfrom
rhaist:rhabugfix/lstm-realtime-safety
Open

Fix LSTM real-time safety (#218)#299
rhaist wants to merge 1 commit into
sdatkinson:mainfrom
rhaist:rhabugfix/lstm-realtime-safety

Conversation

@rhaist

@rhaist rhaist commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #218LSTM is not real-time safe.

LSTMCell::get_hidden_state() returned an Eigen::VectorXf by value, which heap-allocates on every call. Since the LSTM runs sample-by-sample on the audio thread, this allocated once per inter-layer hop and once for the head on every single sample (~48k × num_layers allocations/sec), making process() not real-time safe.

Changes

  • get_hidden_state() now returns a non-owning Eigen::Ref<const VectorXf> view into the internal state instead of a by-value copy.
  • LSTMCell::process_() accepts the same Eigen::Ref so the view binds without copying.
  • The gate matmul is split into a noalias() product plus a separate bias add, so the matrix-vector product evaluates directly into the pre-allocated _ifgo buffer with no temporary.

Tests

Adds tools/test/test_lstm_realtime_safe.cpp, modeled on the existing WaveNet/FiLM real-time-safety tests, using the allocation-tracking harness to assert LSTM::process() performs zero allocations and zero frees across single-layer, multi-layer, multi-channel, large-hidden, and consecutive-call cases.

Verified the new tests catch the regression: 128 allocs / 128 frees before the fix, 0 / 0 after. Full run_tests suite passes; lstm.nam runs clean end-to-end.

LSTMCell::get_hidden_state() returned an Eigen::VectorXf by value, which
heap-allocated on every call. Since the LSTM runs sample-by-sample on the
audio thread, this allocated once per inter-layer hop and once for the head
on every single sample (e.g. ~48k * num_layers allocations/sec), making the
process() hot path not real-time safe.

Return a non-owning Eigen::Ref<const VectorXf> view into the internal state
instead, and accept the same type in LSTMCell::process_() so the view binds
without copying. Also split the gate matmul into a noalias() product plus a
separate bias add so the matrix-vector product evaluates directly into the
pre-allocated buffer.

Add tools/test/test_lstm_realtime_safe.cpp, modeled on the existing
WaveNet/FiLM real-time-safety tests, using the allocation-tracking harness to
assert that LSTM::process() performs zero allocations and zero frees across
single-layer, multi-layer, multi-channel, large-hidden, and consecutive-call
cases. Verified the new tests catch the regression (64 allocs/64 frees over 64
frames before the fix; 0/0 after).
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.

Fix LSTM real-time safety issues

1 participant