NeMo Adapters Support + ASR Adapters
State: closed | Pull request: yes

# What does this PR do ?

Adds support for post-trained Adapter modules to any model that supports it.

**Collection**: [ASR]

# Changelog 
- Adds `AdapterModuleMixin` that is the core framework on which the layered Adapter framework works
- Adds 3 level API for propagating and storing config information of adapters added after model is loaded 
  - ModelPT level - EncoderAdapterModelMixin
  - Encoder level - AdapterModuleMixin
  - Layer level - AdapterModuleMixin
- Adds support for adapters to CTC/RNNT models - Jasper/QuartzNet/Citrinets/ContextNets/Conformers.
- Support `LinearAdapter`

TODO : 
- [x] Add tests
- [x] Add scripts for CTC BPE / RNNT BPE
- [ ] Add Tutorial (separate from PR)
- [x] Add Jenkins test
- [ ] Documentation
  - [x] Core `AdapterModuleMixin`
  - [x] Subclasses / Mixin instances in ASR
  - [ ] Doc page for Adapters in RTD 

# Usage

```python

from nemo.collections.asr.models import ASRModel
from nemo.core import adapter_mixins

# Load the original config of a pretrained model
cfg = ASRModel.from_pretrained('stt_en_citrinet_256', map_location='cpu', return_config=True)

# Override its encoder `_target_`, append the target class name with `Adapter` (conversion for now)
adapter_metadata = adapter_mixins.get_registered_adapter(cfg.encoder._target_)
if adapter_metadata is not None:
    cfg.encoder._target_ = adapter_metadata.adapter_class_path

# Load the model with an encoder that supports Adapters
model = ASRModel.from_pretrained('stt_en_citrinet_256', override_config_path=cfg)

# Add an adapter with some config 
def get_adapter_cfg(in_features=50, dim=100, norm_pos='post'):
    cfg = {
        '_target_': 'nemo.collections.common.parts.adapter_modules.LinearAdapter',
        'in_features': in_features,
        'dim': dim,
        'norm_position': norm_pos,
    }
    return cfg

model.add_adapter('adapter_0', cfg=get_adapter_cfg(in_features=cfg.encoder.jasper[0].filters, dim=5))
assert model.is_adapter_available()

# Enable/Disable adapters before/after training / inference / model building
model.set_enabled_adapters(enabled=False)  # disable all adapters prior to training
model.set_enabled_adapters('adapter_0', enabled=True)  # enable just one adapter by name
model.set_enabled_adapters(enabled=True)  # or enable all adapters for training

# First, Freeze all the weights of the model (not just encoder, everything)
model.freeze()

# Then, Unfreeze just the adapter weights that were enabled above (no part of encoder/decoder/joint/etc)
model.unfreeze_enabled_adapters()

# Adapter module(s) are now ready to train and adapt to new domain.
assert model.num_weights < 1e5

```

# Before your PR is "Ready for review"
**Pre checks**:
- [x] Make sure you read and followed [Contributor guidelines](https://github.com/NVIDIA/NeMo/blob/main/CONTRIBUTING.md)
- [x] Did you write any new necessary tests?
- [x] Did you add or update any necessary documentation?
- [x] Does the PR affect components that are optional to install? (Ex: Numba, Pynini, Apex etc)
  - [x] Reviewer: Does the PR have correct import guards for all optional libraries?
  
**PR Type**:
- [x] New Feature
- [ ] Bugfix
- [ ] Documentation