Inconsistency between the tokenization of `CLIPTokenizer` and `CLIPTokenizerFast` with `openai/clip-vit-base-patch32`
Author: SaulLu
Pull request: no
Labels: name: WIP

## Environment info
<!-- You can run the command `transformers-cli env` and copy-and-paste its output below.
     Don't forget to fill out the missing fields in that output! -->

- `transformers` version: 4.8.2
- Platform: Linux-5.4.104+-x86_64-with-Ubuntu-18.04-bionic
- Python version: 3.7.10
- PyTorch version (GPU?): 1.9.0+cu102 (False)
- Tensorflow version (GPU?): 2.5.0 (False)
- Flax version (CPU?/GPU?/TPU?): not installed (NA)
- Jax version: not installed
- JaxLib version: not installed
- Using GPU in script?: no
- Using distributed or parallel set-up in script?: no

### Who can help
@patil-suraj, I think you worked on CLIP, maybe you could help me by confirming that this behavior is not normal. If it is and no one can deal with it first, I'd be happy to try to fix it.
<!-- Your issue will be replied to more quickly if you can figure out the right person to tag with @
 If you know how to use git blame, that is the easiest way, otherwise, here is a rough guide of **who to tag**.
 Please tag fewer than 3 people.

Models:

- albert, bert, xlm: @LysandreJik
- blenderbot, bart, marian, pegasus, encoderdecoder,  t5: @patrickvonplaten, @patil-suraj
- longformer, reformer, transfoxl, xlnet: @patrickvonplaten
- fsmt: @stas00
- funnel: @sgugger
- gpt2: @patrickvonplaten, @LysandreJik
- rag: @patrickvonplaten, @lhoestq
- tensorflow: @Rocketknight1

Library:

- benchmarks: @patrickvonplaten
- deepspeed: @stas00
- ray/raytune: @richardliaw, @amogkam
- text generation: @patrickvonplaten
- tokenizers: @LysandreJik
- trainer: @sgugger
- pipelines: @LysandreJik

Documentation: @sgugger

Model hub:

- for issues with a model report at https://discuss.huggingface.co/ and tag the model's creator.

HF projects:

- datasets: [different repo](https://github.com/huggingface/datasets)
- rust tokenizers: [different repo](https://github.com/huggingface/tokenizers)

Examples:

- maintained examples (not research project or legacy): @sgugger, @patil-suraj
- research_projects/bert-loses-patience: @JetRunner
- research_projects/distillation: @VictorSanh

 -->

## Information

Model I am using (Bert, XLNet ...): CLIP

## To reproduce

The easiest way to reproduce is to open [this google colab](https://colab.research.google.com/drive/1JzlYtuG4MdAKl8lPI5PkqGcYbdM3N24x?usp=sharing)

Steps to reproduce the behavior:

1. Import the slow and fast CLIP tokenizers from the transformers library and eventualy the tokenizer of https://github.com/openai/CLIP
```
from transformers import CLIPTokenizer, CLIPTokenizerFast
tokenizer_slow = CLIPTokenizer.from_pretrained("openai/clip-vit-base-patch32")
tokenizer_fast = CLIPTokenizerFast.from_pretrained("openai/clip-vit-base-patch32")
```
```
from CLIP import clip as clip_orig
```
2. Tokenize the same text with the 3 tokenizers
```
text = "A photo of a cat"
context_length = 77
```
```
tokens_ids_orig = clip_orig.tokenize(text)
tokens_ids_slow = tokenizer_slow.encode(text, padding="max_length", max_length=context_length, return_tensors='pt')
tokens_ids_fast = tokenizer_fast.encode(text, padding="max_length", max_length=context_length, return_tensors='pt')
```
3. Compare the outputs
```
(tokens_ids_orig == tokens_ids_slow).sum() == context_length
```
Output: `True`
```
(tokens_ids_orig == tokens_ids_fast).sum() == context_length
```

<!-- If you have code snippets, error messages, stack traces please provide them here as well.
     Important! Use code tags to correctly format your code. See https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks#syntax-highlighting
     Do not use screenshots, as they are hard to read and (more importantly) don't allow others to copy-and-paste your code.-->

## Expected behavior

I think I would have expected the slow and fast versions to tokenize the text in the same way. 

<!-- A clear and concise description of what you would expect to happen. -->


Comments:
Great catch! 
Indeed, this is not normal. Feel free to give it a try to fix this as I won't be able to assign time for it this week, thanks :) 

3 issues that are causing this in-consistency

- The fast tokenizer was using `ByteLevel` `decoder ` which was not removing the end of word suffix `</w>`. Using `BPEDecoder`  fixes this
- CLIP uses `bos` and `eos` tokens, but the current post-processor is `ByteLevel` processor which does not add these, using `TemplateProcessing` instead fixes this.
- Unlike GPT2's BPE tokenizer, CLIP's BPE does not represent space with `Ġ`. It instead repalces `</w>` with space during decoding. But the `BPE` tokenizer in `tokenizers` always seems to replace space with `Ġ`, which is the only remaining issue. 

```python
tokenizer_slow = CLIPTokenizer.from_pretrained("openai/clip-vit-base-patch32")
tokenizer_fast = CLIPTokenizerFast.from_pretrained("openai/clip-vit-base-patch32", from_slow=True)

text = "A photo of a cat"
tokenizer_slow.tokenize(text)
# ['a</w>', 'photo</w>', 'of</w>', 'a</w>', 'cat</w>']

tokenizer_fast.tokenize(text)
# ['a</w>', 'Ġ', 'photo</w>', 'Ġ', 'of</w>', 'Ġ', 'a</w>', 'Ġ', 'cat</w>']
```



Is there any way to disable this behavior @n1t0 @SaulLu ?



@patil-suraj Hi, I wonder if this issue is solved? When will be this fix becomes official? Thanks!

I'm really sorry for the delay. I have investigated a bit and I think that unfortunately the last problem is not limited to the fact that spaces are replaced by `Ġ`.

For example, here is the output on another example:
```python
tokenizer_slow = CLIPTokenizer.from_pretrained("openai/clip-vit-base-patch32")
tokenizer_fast = CLIPTokenizerFast.from_pretrained("openai/clip-vit-base-patch32", from_slow=True)

text = "A\n'll 11p223RF☆ho!!to? of a cat"
tokenizer_slow.tokenize(text)
# ['a</w>', "'ll</w>", '1</w>', '1</w>', 'p</w>', '2</w>', '2</w>', '3</w>', 'rf</w>', 'âĺĨ</w>', 'ho</w>', '!!</w>', 'to</w>', '?</w>', 'of</w>', 'a</w>', 'cat</w>']

tokenizer_fast.tokenize(text)
# ['a</w>', 'Ġ', "'</w>", 'll</w>', 'Ġ', '1', '1</w>', 'p</w>', '2', '2', '3</w>', 'rf</w>', 'âĺĨ</w>', 'ho</w>', '!!</w>', 'to</w>', '?</w>', 'Ġ', 'of</w>', 'Ġ', 'a</w>', 'Ġ', 'cat</w>']
```

I think that we also need a pre tokenizer that reproduces the split  induced in [this line](https://github.com/openai/CLIP/blob/main/clip/simple_tokenizer.py#L124) thanks to this regex: `r"""<\|startoftext\|>|<\|endoftext\|>|'s|'t|'re|'ve|'m|'ll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+"""`. I think we could use [`tokenizers.pre_tokenizers.Split`](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#tokenizers.pre_tokenizers.Split) with [tokenizers.pre_tokenizers.Sequence](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#tokenizers.pre_tokenizers.Sequence) but for the moment I couldn't make it work.

At this point, the only solution I can propose that comes close (but doesn't match entirely) to the correct behavior is to replace the `tokenizer.pre_tokenizer=pre_tokenizers.ByteLevel(add_prefix_space=False)` line of the `CLIPConverter` class into `convert_slow_tokenizer.py` with :
```python
        tokenizer.pre_tokenizer = pre_tokenizers.Sequence(
            [
                pre_tokenizers.pre_tokenizers.WhitespaceSplit(),
                pre_tokenizers.ByteLevel(
                    add_prefix_space=False,
                ),
            ]
        )
```
This would give on the previous example: 
```
tokenizer_slow = CLIPTokenizer.from_pretrained("openai/clip-vit-base-patch32")
tokenizer_fast = CLIPTokenizerFast.from_pretrained("openai/clip-vit-base-patch32", from_slow=True)

text = "A\n'll 11p223RF☆ho!!to? of a cat"
tokenizer_slow.tokenize(text)
# ['a</w>', "'ll</w>", '1</w>', '1</w>', 'p</w>', '2</w>', '2</w>', '3</w>', 'rf</w>', 'âĺĨ</w>', 'ho</w>', '!!</w>', 'to</w>', '?</w>', 'of</w>', 'a</w>', 'cat</w>']

tokenizer_fast.tokenize(text)
# ['a</w>', "'ll</w>", '1', '1</w>', 'p</w>', '2', '2', '3</w>', 'rf</w>', 'âĺĨ</w>', 'ho</w>', '!!</w>', 'to</w>', '?</w>', 'of</w>', 'a</w>', 'cat</w>']
```


@SaulLu Thanks for providing this temporal solution. I hope this issue could be fixed soon and merged into the huggingface official release by @patil-suraj @n1t0 

Thank you for investigating this @SaulLu ! There is one more difference which I'm not sure how to handle in fast tokenizers. 
Since CLIP is trained on noisy web alt text, it uses `ftfy` to fix the text which also changes the tokenization.

@n1t0 Would be nice if you let us know if this is something that can be supported in fast tokenizers.

I just thought about this issue and I think it would be important to fix it quickly because a user who would use the fast version of this tokenizer could really have bad surprises.

1. in the very short term, it is probably safer to remove the fast version of the tokenizer from the library. Indeed I think that fixing this tokenizer will require a lot of discussions (or even a new release of the Tokenizers library)
2. I tried to work to create a fast tokenizer as faithful as possible to the slow version in [this PR](https://github.com/huggingface/transformers/pull/15067). Nevertheless, I really need to discuss this fix with you. I explain in more detail the points to discuss in the PR. :smile: 



Hey, is this fix as of now?