`BertTokenizerFast.vocab.keys()` does not return a fixed order sequence
Author: function2-llx
Pull request: no

## Environment info
- `transformers` version: 4.12.3
- Platform: Linux-5.4.0-1048-aws-x86_64-with-glibc2.27
- Python version: 3.9.7
- PyTorch version (GPU?): 1.10.0 (False)
- Tensorflow version (GPU?): not installed (NA)
- 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

Tokenizers: @LysandreJik 

## Information

I'm using `BertTokenizer` and `BertTokenizerFast`, and I found that `BertTokenizerFast.vocab.keys()` does not return a fixed order sequence, while `BertTokenizer` does.

The problem arises when using:
* [ ] the official example scripts: (give details below)
* [x] my own modified scripts: (give details below)

The tasks I am working on is:
* [ ] an official GLUE/SQUaD task: (give the name)
* [x] my own task or dataset: (give details below)

## To reproduce

Steps to reproduce the behavior:

1. Create a python script named `test.py` with the following code, which simply loads a pre-trained BERT tokenizer, acquires its vocabulary with `tokenizer.vocab.key()` and dumps the vocabulary.
```python
import json
import random
from sys import stdout
from transformers import BertTokenizerFast
random.seed(2333)
tokenizer = BertTokenizerFast.from_pretrained('bert-base-uncased')
vocab_tokens = list(tokenizer.vocab.keys())
json.dump(vocab_tokens, stdout, indent=4, ensure_ascii=False)
```
2. Run the script twice with stdout redirecting to two different files, and compare the two outputs, will find they are different.
```zsh
➜  python test.py > run1.json
➜  python test.py > run2.json
➜  diff -q run1.json run2.json 
Files run1.json and run2.json differ
```
3. Change `BertTokenizerFast` to `BertTokenizer` in the script and execute commands of step 2 again, the two outputs are the same this time.

Screenshot of my results for `BertTokenizerFast` and `BertTokenizer`:

![image](https://user-images.githubusercontent.com/38486514/142173100-b67cb38f-76ec-47e6-8cd0-d2cbd2196b69.png)
![image](https://user-images.githubusercontent.com/38486514/142173975-b917af77-3b3c-430c-a22b-22d29508d861.png)

## Expected behavior

Like `BertTokenizer`, `BertTokenizerFast` should also output the same vocabulary across different runs.


Comments:
Well, a dictionary-like object (in which the vocab is stored) is naturally unordered in most programming languages due to the data structure behind.

The vocab order that `BertTokenizer` yields is fixed because it leverages `OrderedDict`, which is a special dict class making itself ordered. Additionally, the iterator order of Python's `dictview` object is guaranteed based on insertion order since Python 3.7 (See [this](https://docs.python.org/3.8/library/stdtypes.html#dictionary-view-objects)). Therefore, both mechanisms ensure you see a fixed vocab list.

However, `BertTokenizerFast` is backed by Rust programming language, which uses `HashMap` to store the vocab, and this data structure is naturally unordered. That's why you see the difference between them.

> Well, a dictionary-like object (in which the vocab is stored) is naturally unordered in most programming languages due to the data structure behind.
> 
> The vocab order that `BertTokenizer` yields is fixed because it leverages `OrderedDict`, which is a special dict class making itself ordered. Additionally, the iterator order of Python's `dictview` object is guaranteed based on insertion order since Python 3.7 (See [this](https://docs.python.org/3.8/library/stdtypes.html#dictionary-view-objects)). Therefore, both mechanisms ensure you see a fixed vocab list.
> 
> However, `BertTokenizerFast` is backed by Rust programming language, which uses `HashMap` to store the vocab, and this data structure is naturally unordered. That's why you see the difference between them.

Thanks for the explanation, and I agree that this is the reason. 

However when programme with Python3.7+, I would assume that all `dict` objects will be ordered, at least does not change across different runs. Unfortunately, this wrong assumption brings my specific implementation a bug and takes me plenty of time to locate it. 

I think many people may have similar thoughts like this, and people may not always keep in mind that the fast tokenizer has a Rust backend, which may lead to different behavior. I want to ask another more general question, is it necessary to keep consistent behavior between Python-backend tokenizer and Rust-backend tokenizer? Or shall we document this and give some warning?

In general, I think we should not make an assumption that a `dict`, or let's say a hash-backed object, is ordered since hash table is a naturally unordered data structure. Python is a special case of it and only with 3.7+ guarantees dict iterator order. Before Python 3.7, this is a CPython implementation detail of dict and can vary upon different implementations. 

Moreover, although `BertTokenizer` uses `OrderedDict` to guarantee the order of vocab, the [docs](https://huggingface.co/transformers/internal/tokenization_utils.html#transformers.tokenization_utils_base.PreTrainedTokenizerBase.get_vocab) only states `a dict is returned`, meaning that even `BertTokenizer` is using `OrderedDict`, other models maybe not. Additionally, `transformers` is Python version agnostic which currently supports Python 3.6+, it cannot ensure dict ordered characteristics among different Python versions are consistent.

Therefore, I perceive that the different behavior of this is fairly normal and acceptable, and giving a note or warning in the docs is also welcomed, which can make it more unequivocal for people.

@qqaatw Thanks, this makes sense! 

Currently, I would suggest one may want to sort their keys from the `dict` if they want an absolutely deterministic order, especially when the dict is from some unknown implementation (maybe this applies to the the whole Python programming).

This issue has been automatically marked as stale because it has not had recent activity. If you think this still needs to be addressed please comment on this thread.

Please note that issues that do not follow the [contributing guidelines](https://github.com/huggingface/transformers/blob/master/CONTRIBUTING.md) are likely to be ignored.