Import structure & first three model refactors
Author: LysandreJik
Pull request: yes

## Simplifying the import structure

This is the first PR that aims to facilitate adding new models by reducing the overhead caused by the `__init__.py` contributions.

It introduces three new concepts:
- The `export` keyword, which accepts the `backends` tuple as input.
- The `define_import_structure`, which accepts a filepath as input.
- The `__all__` keyword for easy exports

This approach adds a little overhead to what is currently done as it needs to open files and do some string checks across files. It is negligible compared to framework instantiations like `torch` but should still be highlighted; this approach is extremely specific to `transformers` and to the fact that we want to reduce the barrier of contributions to a minimum.

### `@export`

The `export` keyword acts as an exporter. For now this is limited to the `src/transformers/models` path only, but if accepted, it can be propagated to the entire repository.

Using the `@export` decorator on a class or method exports it to be importable by third-parties. In doing so, it accepts a tuple of `backends`. In case a backend isn't installed, the object can still be imported; but using any method or attribute on this object will raise an error. 

This method aims to deprecate and replace the use of `dummy_xxx` modules and objects.

### `define_import_structure`

This method relies on all the objects in a module being correctly marked with `@register` and with the correct backends. It replaces the current complex dict structure dedicated to Lazy loading, and continues supporting lazy loading.

### The `__all__` keyword

This keyword is used here principally for static type checkers that cannot understand the `@register` keyword. Correctly exporting objects through this enables correct type hinting/type checking.

The `define_import_structure` keyword acts in conjunction with the `__all__` method and the `@export` decorator to correctly identify which imports depend on what backends so as to reflect that in the lazy import scheme. 

The rest of the model refactor is here: https://github.com/huggingface/transformers/pull/31330

Comments:
I'd be interested in having your review when you find the time @amyeroberts @ArthurZucker @Wauplin.

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/main/CONTRIBUTING.md) are likely to be ignored.

Not stale

The docs for this PR live [here](https://moon-ci-docs.huggingface.co/docs/transformers/pr_31329). All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

Thanks all for your reviews! This should now be **ready** to merge!

Since the last review cycle, here are the changes that have been done:

### `__all__` as the default

The items written down in `__all__` now do NOT need to be exported with `export` (previously `register`); they are naturally seen as exported.

According to the file in which they are defined, specific backends are given to them.

Namely these are the assumptions:
```
BASE_FILE_REQUIREMENTS = {
    lambda e: 'modeling_tf_' in e: ('tf',),
    lambda e: 'modeling_flax_' in e: ('flax',),
    lambda e: 'modeling_' in e: ('torch',),
    lambda e: e.startswith('tokenization_') and e.endswith('_fast'): ('tokenizers',),
}
```

Therefore:
- A file containing `modeling_tf_` will add a `tf` requirement
- A file containing `modeling_flax_` will add a `flax` requirement
- A file containing `modeling_` that did not trigger one of the two above will add a `torch` requirement
- A file starting with `tokenization_` and ending with `_fast` (the .py is removed) will add a `tokenizers` requirement

### Register -> Export

`register` has been renamed to `export`. Given the above changes to `__all__`, the `export` keyword is now solely needed for exported objects that need additional or different backends than the ones given by the filename in which they reside.

An example in this PR is the `AlbertTokenizer`, which has a `sentencepiece` requirement but lives in `tokenization_albert.py`.

We cannot make assumptions according to the filename, so we manually export it with the right requirements:
```
@export(backends=("sentencepiece",))
class AlbertTokenizer(PreTrainedTokenizer):
    """
```


---

Apart from that, the code was cleaned up, optimized, and benchmarked. On the latest commits, the difference is not visible when importing from `transformers`: each `__init__` file will add an overhead of ~.67ms on my machine up to 1.5ms for the very first instantiation.

On https://github.com/huggingface/transformers/pull/31330, the total amount of time added for all modeling files stands between 150ms and 200ms.

The one comment I have left for later is yours @Wauplin with which I agree, but I don't have the bandwidth to get to right now: https://github.com/huggingface/transformers/pull/31329#discussion_r1634655893



There are some nice next steps following this:
- This PR takes care of the rest of the models: https://github.com/huggingface/transformers/pull/31330
- Once all models are handled, the `export` and `__all__` keywords can be propagated through the root of the repo
- Finally, the dummies can be removed and the main `__init__` can be cleaned accordingly

As seen with @ArthurZucker and @amyeroberts, merging with the first three models.