Repository: google/flax

Issue:
lm1b sampler update is out of bound?
Hi,

_Disclaimer: I'm not confident if this is some intended behavior._

I think in the lm1b example, the prediction sampler could write to indices out of bound of the result.
The particular offending line is:  https://github.com/google/flax/blob/master/examples/lm1b/temperature_sampler.py#L109.

Since `i <= max_decode_len` according to https://github.com/google/flax/blob/master/examples/lm1b/temperature_sampler.py#L75, and since `prompt_inputs.shape[1] == max_decode_len`, the last iteration (or two iterations?) of while loop would overwrite to `prompt_inputs[..., max_decode_len - 1]` (due to XLA semantics). 

It's either a subtle bug (it won't raise cause error), or there's something I'm not understanding about the sampling procedure.


Discussion:
Hmm yes, this code looks a bit odd to me. It seems we should stop 2 iterations earlier, so `not_at_end` should be defined as follows:

```python
not_ at_end = (i < max_decode_len - 1)
```

@levskaya who I think wrote this code: what do you think of this?
Reassigning to @zaxtax who will take a look at this problem.

Resolving patch:
diff --git a/examples/lm1b/temperature_sampler.py b/examples/lm1b/temperature_sampler.py
--- a/examples/lm1b/temperature_sampler.py
+++ b/examples/lm1b/temperature_sampler.py
@@ -57,7 +57,7 @@ def temperature_sample(prompt_inputs,
   # initial loop PRNGKey
   rng0 = prng_key
   # loop position counter.
-  i0 = jnp.array(0)
+  i0 = jnp.array(-1)
   # per batch-item holding current token in loop.
   token0 = jnp.zeros((batch_size, 1), dtype=jnp.int32)
   # per batch-item state bit indicating if sentence has finished.
@@ -72,7 +72,7 @@ def sampling_loop_cond_fn(state):
     """Sampling loop termination condition."""
     (i, _, _, _, ended, _) = state
     # Have we reached max decoding length?
-    not_at_end = (i < max_decode_len)
+    not_at_end = (i < max_decode_len - 1)
     # Have all sampled sequences reached an end marker?
     all_sequences_ended = jnp.all(ended)
     return not_at_end & (~all_sequences_ended)


Test patch:
diff --git a/examples/lm1b/temperature_sampler_test.py b/examples/lm1b/temperature_sampler_test.py
new file mode 100644
--- /dev/null
+++ b/examples/lm1b/temperature_sampler_test.py
@@ -0,0 +1,45 @@
+# Copyright 2022 The Flax Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from absl.testing import absltest
+import jax
+import jax.numpy as jnp
+import numpy as np
+
+from temperature_sampler import temperature_sample
+
+
+jax.config.update('jax_disable_most_optimizations', True)
+
+
+class TestTemperatureSampler(absltest.TestCase):
+  def test_temperature_sampler(self):
+
+    tokens = jnp.array([[5, 0, 0, 0]], dtype=jnp.int32)
+    cache = None
+    key = jax.random.PRNGKey(0)
+
+    def tokens_to_logits(tokens, cache):
+        jax.debug.print("tokens: {}", tokens)
+        logits = jax.nn.one_hot(tokens[..., -1:] + 1, 10)
+        logits = jnp.where(logits < 0.5, float('-inf'), logits)
+        logits = logits.squeeze(axis=1)
+        return logits, cache
+
+    new_tokens = temperature_sample(tokens, cache, tokens_to_logits, key, topk=5)
+
+    np.testing.assert_array_equal(new_tokens, [[5, 6, 7, 8]])
+
+if __name__ == '__main__':
+  absltest.main()
\ No newline at end of file