Skip to content

Stop storing None in recurrent dropout state - #317

Open
rootkiller6788 wants to merge 4 commits into
google-deepmind:v2from
rootkiller6788:fix-recurrent-dropout-tf-function
Open

rootkiller6788 wants to merge 4 commits into
google-deepmind:v2from
rootkiller6788:fix-recurrent-dropout-tf-function

Conversation

@rootkiller6788

Copy link
Copy Markdown

Fixes #161.

snt.dynamic_unroll(snt.lstm_with_recurrent_dropout(...), inputs, initial_state) works in eager mode but crashes with ValueError: None values not supported as soon as it's called inside a tf.function. The dropout wrapper's initial_state used None for state elements that aren't dropped (the cell), and dynamic_unroll carries that state through a tf.while_loop, which can't handle None leaves.

I changed maybe_dropout to return an all-ones mask instead of None for elements that shouldn't be dropped. The state stays a plain nest of tensors, and since s * ones == s the dropout behaviour is identical. The existing eager tests still pass, and I added a regression test that runs dynamic_unroll under tf.function (which is what actually exercises the while-loop path) and checks initial_state has no None leaves.

Verified against tensorflow 2.12: the new test fails on the old code and passes with this change; the full recurrent_test.py run is green (94 passed, 15 skipped on CPU-only for GPU/TPU-specific tests).

lstm_with_recurrent_dropout's initial_state used None for state elements
that aren't dropped (the cell). That state is carried by dynamic_unroll
through a tf.while_loop when running inside tf.function, and a loop can't
carry None, so the call failed with "None values not supported".

Give those elements an all-ones mask instead. The state is then a plain
nest of tensors, and since multiplying by one is a no-op the dropout
behaviour is unchanged.
dynamic_unroll only exercises the tf.while_loop path in graph mode, so
the existing eager test never hit the crash. Wrap the unroll in a
tf.function and also check that initial_state has no None leaves.

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replacing the undropped cell mask with tf.ones_like(s) fixes the None leaf, but it also adds a full [batch, hidden] tensor to the recurrent state and carries/multiplies it on every tf.while_loop iteration solely to represent identity. For large LSTMs this is avoidable state and memory traffic. A scalar one tensor of the same dtype (broadcast on multiply), or another tensor sentinel, satisfies the loop-state requirement without duplicating the cell-state shape. Please keep the no-None fix without introducing a full-size identity mask.

tf.ones_like(s) is a full [batch, hidden] tensor, and it ends up in the
loop state that dynamic_unroll re-feeds on every step just so it can be
multiplied by one. A scalar one of the same dtype broadcasts the same way
and costs nothing to carry.
Also covers the case where a resizeable identity mask creeps back in.
@rootkiller6788

Copy link
Copy Markdown
Author

Thanks @sylvesterkaczmarek — agreed, and this is addressed in the commits I just pushed. maybe_dropout now returns tf.ones([], dtype=s.dtype) for undropped elements instead of tf.ones_like(s), so the identity mask is a scalar of the same dtype that broadcasts on multiply. It satisfies the loop-state requirement without adding a [batch, hidden] tensor to the recurrent state, and the state still has no None leaves. I also extended the regression test to pin the shape — assertIn((), [tuple(m.shape) for m in tree.flatten(dropout_masks)]) — so a full-size mask can't creep back in. Re-verified on the updated head with TensorFlow 2.16.2 / Python 3.11 (CPU): the issue #161 repro fails on v2 with ValueError: None values not supported and passes with the fix, and the full recurrent_test.py is 94 passed, 15 skipped.

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rechecked feb31c0. Undropped recurrent elements now use a scalar one of the cell-state dtype, so the tf.function loop state stays free of None without carrying a full [batch, hidden] identity tensor. The regression pins the scalar shape. My concern is resolved.

This branch has not been deployed

No deployments
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.

v2: dynamic_unroll using lstm_with_recurrent_dropout doesn't work

2 participants