Stop storing None in recurrent dropout state - #317
rootkiller6788 wants to merge 4 commits into
Conversation
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
left a comment
There was a problem hiding this comment.
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.
|
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
left a comment
There was a problem hiding this comment.
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.
Fixes #161.
snt.dynamic_unroll(snt.lstm_with_recurrent_dropout(...), inputs, initial_state)works in eager mode but crashes withValueError: None values not supportedas soon as it's called inside atf.function. The dropout wrapper'sinitial_stateusedNonefor state elements that aren't dropped (the cell), anddynamic_unrollcarries that state through atf.while_loop, which can't handleNoneleaves.I changed
maybe_dropoutto return an all-ones mask instead ofNonefor elements that shouldn't be dropped. The state stays a plain nest of tensors, and sinces * ones == sthe dropout behaviour is identical. The existing eager tests still pass, and I added a regression test that runsdynamic_unrollundertf.function(which is what actually exercises the while-loop path) and checksinitial_statehas noNoneleaves.Verified against tensorflow 2.12: the new test fails on the old code and passes with this change; the full
recurrent_test.pyrun is green (94 passed, 15 skipped on CPU-only for GPU/TPU-specific tests).