Support nested NeMo models
State: closed | Pull request: yes
Labels: name: core

# What does this PR do ?

Adds support for nested NeMo models (with resources).

It's possible to instantiate and save models with NeMo submodules. Artifacts are correctly saved and restored.

**Collection**: [core]

# Changelog 
- Add specific line by line info of high level changes in this PR.

# Usage

Example usage:
- 3 ways to instantiate child models: 
  - use subconfig directly
  - use `child_model_path` with `.nemo` checkpoint path to load the model
  - use pretrained model
- child model can contain artifacts
- child model config can be changed after parent model instantiation. Config will be saved when saving parent model

```python
from nemo.core.classes import ModelPT

class ChildModel(ModelPT):
    ...  # implement necessary methods

class ParentModel(ModelPT):
    def __init__(self, cfg, trainer=None):
        super().__init__(cfg=cfg, trainer=trainer)

        # optionally annotate type for IDE autocompletion and type checking
        self.child_model: Optional[ChildModel]
        if cfg.get("child_model") is not None:
            # load directly from config
            # either if config provided initially, or automatically
            # after model restoration
            self.register_nemo_submodule(
                "child_model",
                config_field="child_model",
                model=ChildModel(self.cfg.child_model),
            )
        elif cfg.get('child_model_path') is not None:
            # load from .nemo model checkpoint
            # while saving, config will be automatically assigned/updated
            # in cfg.child_model
            self.register_nemo_submodule(
                "child_model",
                config_field="child_model",
                model=ChildModel.restore_from(self.cfg.child_model_path),
            )
        elif cfg.get('child_model_name') is not None:
            # load from pretrained model
            # while saving, config will be automatically assigned/updated
            # in cfg.child_model
            self.register_nemo_submodule(
                "child_model",
                config_field="child_model",
                model=ChildModel.from_pretrained(self.cfg.child_model_name),
            )
        else:
            self.child_model = None
```

# 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?
- [ ] Did you add or update any necessary documentation?
- [ ] Does the PR affect components that are optional to install? (Ex: Numba, Pynini, Apex etc)
  - [ ] Reviewer: Does the PR have correct import guards for all optional libraries?
  
**PR Type**:
- [x] New Feature
- [ ] Bugfix
- [ ] Documentation

If you haven't finished some of the above items you can still open "Draft" PR.


## Who can review?

Anyone in the community is free to review the PR once the checks have passed. 
[Contributor guidelines](https://github.com/NVIDIA/NeMo/blob/main/CONTRIBUTING.md) contains specific people who can review PRs to various areas.

# Additional Information
* Related to # (issue)