optimize stopping strings processing
diff --git a/modules/callbacks.py b/modules/callbacks.py
index 51ecbdd72d..a91c94a04d 100644
--- a/modules/callbacks.py
+++ b/modules/callbacks.py
@@ -9,25 +9,30 @@
 import modules.shared as shared
 
 
-# Copied from https://github.com/PygmalionAI/gradio-ui/
 class _SentinelTokenStoppingCriteria(transformers.StoppingCriteria):
 
     def __init__(self, sentinel_token_ids: list, starting_idx: int):
         transformers.StoppingCriteria.__init__(self)
         self.sentinel_token_ids = sentinel_token_ids
         self.starting_idx = starting_idx
+        self.shortest = min([x.shape[-1] for x in sentinel_token_ids])
 
     def __call__(self, input_ids: torch.LongTensor, _scores: torch.FloatTensor) -> bool:
         for sample in input_ids:
             trimmed_sample = sample[self.starting_idx:]
+            trimmed_len = trimmed_sample.shape[-1]
+            if trimmed_len < self.shortest:
+                continue
 
-            for i in range(len(self.sentinel_token_ids)):
-                # Can't unfold, output is still too tiny. Skip.
-                if trimmed_sample.shape[-1] < self.sentinel_token_ids[i].shape[-1]:
+            for sentinel in self.sentinel_token_ids:
+                sentinel_len = sentinel.shape[-1]
+                if trimmed_len < sentinel_len:
                     continue
-                for window in trimmed_sample.unfold(0, self.sentinel_token_ids[i].shape[-1], 1):
-                    if torch.all(torch.eq(self.sentinel_token_ids[i][0], window)):
-                        return True
+
+                window = trimmed_sample[-sentinel_len:]
+                if torch.all(torch.eq(sentinel, window)):
+                    return True
+
         return False
 
 

# TLDR

Having a bunch of `custom_stopping_strings` made generation really slow. This PR makes it go fast again.

# Technical detail

The code for stopping tokens essentially just, repeatedly scanned the *entire* generated content for stop strings. In theory this sounds fine, until you realize: the callback is ran once per token generated. (This is the core principle for how streaming even works! It uses a fake stop string processor as a callback provider!)

Which means, in practice, that the stop string calculations get slower and slower as the number of tokens gets longer, for no actual reason!

When you have many stop strings x many tokens being generated, you have exponential slowdown.

I rewrote the code to only check the most recently generated token(s) for a match.

I also added a few small bonus opti tricks (precomputed a few values to avoid repeated calculation of unchanging values).

If you consider stop string count to be constant and number of tokens as `n`, this converts the algorithm from `O(n)` to `O(1)` time complexity. If you consider stop-string count to be variable `k` then it changes from `O(nk)` to `O(k)`.

# When It Matters

Only matters if you have a bunch of stopping strings. For my test, I run LLaMA-7B with a bunch of random stop strings: `"waffle", "taco", "waffles", "tacos", "potato", "potatoes", "chihuahua", "chihuahuas", "dog", "dogs", "doggos", "doggo", "doggy", "doggies", "long word", "long words", "short word", "short words", "fsa", "afsfsa", "asfw2q", "124r", "5231", "421", "67853", "1223", "1224112", "6694727", "211245219", "12124", "52153", "asggaghd", "hgdasd", "saftwq623q", "adbagsbgas", "asgghewet", "t3w1253wet", "235teqwhsd", "adsghd5a", "523aega"`

This was originally reported as an issue by `DeSinc` on Discord, who had 30 stop strings and presumably a much more legitimate set of stop strings than my random text lol.

This actually also applies when there aren't so many stop tokens, albeit less difference overall. This matters more for small models (like 7B) because a smaller percentage of time is spent in the GPU generating vs. on the CPU doing side processing like stop token checks. For larger models that only get 1t/s anyway, the time difference proportionally matters less.

# Testing

I tested (A) the speed boost and (B) that stop strings work as intended, both on `--no-stream` and streaming mode, in chat mode.

Everything worked as intended in my testing. In regards to performance, I present a testing result table, and before/after gifs to show the real-world difference.

Testing on LLaMA-7B generating a paragraph:

stops | Old | New
---|---|---
43 stop strings | potato | 41t/s
3 stop strings (default) | 33t/s | 43t/s

As you can see, on the new code, even without extra stopping strings, the tokens/sec is actually faster! 43 stop strings+old I listed as `potato` because, well, see gif below. It starts fast but rapidly slows to a crawl. Roughly 2t/s maybe when it hit the 200 token gen limit.

### Before:

![stopstring_slowdown](https://github.com/oobabooga/text-generation-webui/assets/4000772/7e1c85e4-bc9e-4538-b837-6baaed51172a)

### After:

![stopstring_fast](https://github.com/oobabooga/text-generation-webui/assets/4000772/abdc2bb6-e1a6-47cd-a3dc-009b891ea7a2)

You can see in the "Before" that as more tokens are generated, it gets progressively slower, as it is a more aggressive loop.

