Repository: deepset-ai/haystack

Issue:
Autoupload models to huggingface hub after training
**Is your feature request related to a problem? Please describe.**
When training with FARMReader finishes, there should be an option to upload it automatically to HF hub.

**Describe the solution you'd like**
Add option and template to auto upload trained / fine-tuned model to HF.

**Describe alternatives you've considered**
Implement helper function to upload model to HF manually. Maybe we go for that if the selection of the model snapshot involves human interaction.

**Tasks**:
- [ ] add `hf_token`, `hf_repo`, `hf_branch`, `hf_model_card` params to `FARMReader.train()`, `DensePassageRetriever.train()` and `TableTextRetriever.train()`
- [ ] create repo if not exists
- [ ] convert model into huggingface format
- [ ] upload model files and model card
- [ ] provide model card template
- [ ] write documentation about how to use it


Discussion:
Using [huggingface_hub lib](https://github.com/huggingface/huggingface_hub) we can [create a repo](https://huggingface.co/docs/hub/how-to-upstream#%60Repository%60) and upload them model files. To add a model card, we simply have to upload a README.md according to [this](https://huggingface.co/docs/hub/model-repos#what-are-model-cards-and-why-are-they-useful) structure.
Additionally we might want to enable users to specify a version of the model. This could be achieved by using [branches](https://huggingface.co/docs/hub/how-to-upstream#branch) in the model repo.
Sentencetranformers lib also offers this kind of functionality. Maybe we can get some inspiration from there, too.
Additionally, it might make sense to make this available for distilled models too. Something that would allow you to do:

```
student.distil_intermediate_layers_from(teacher, data_dir=".", train_filename="augmented_dataset.json", use_gpu=True)
student.distil_prediction_layer_from(teacher, data_dir="data/squad20", train_filename="dev-v2.0.json", use_gpu=True)
student.push_to_hf()
```

In this case, a helper function might make more sense though as there won't be a .train() to provide the HF options to.


Resolving patch:
diff --git a/haystack/nodes/reader/farm.py b/haystack/nodes/reader/farm.py
--- a/haystack/nodes/reader/farm.py
+++ b/haystack/nodes/reader/farm.py
@@ -4,8 +4,12 @@
 import multiprocessing
 from pathlib import Path
 from collections import defaultdict
+import os
+import tempfile
 from time import perf_counter
+
 import torch
+from huggingface_hub import create_repo, HfFolder, Repository
 
 from haystack.errors import HaystackError
 from haystack.modeling.data_handler.data_silo import DataSilo, DistillationDataSilo
@@ -688,6 +692,58 @@ def save(self, directory: Path):
         self.inferencer.model.save(directory)
         self.inferencer.processor.save(directory)
 
+    def save_to_remote(
+        self, repo_id: str, private: Optional[bool] = None, commit_message: str = "Add new model to Hugging Face."
+    ):
+        """
+        Saves the Reader model to Hugging Face Model Hub with the given model_name. For this to work:
+        - Be logged in to Hugging Face on your machine via transformers-cli
+        - Have git lfs installed (https://packagecloud.io/github/git-lfs/install), you can test it by git lfs --version
+
+        :param repo_id: A namespace (user or an organization) and a repo name separated by a '/' of the model you want to save to Hugging Face
+        :param private: Set to true to make the model repository private
+        :param commit_message: Commit message while saving to Hugging Face
+        """
+        # Note: This function was inspired by the save_to_hub function in the sentence-transformers repo (https://github.com/UKPLab/sentence-transformers/)
+        # Especially for git-lfs tracking.
+
+        token = HfFolder.get_token()
+        if token is None:
+            raise ValueError(
+                "To save this reader model to Hugging Face, make sure you login to the hub on this computer by typing `transformers-cli login`."
+            )
+
+        repo_url = create_repo(token=token, repo_id=repo_id, private=private, repo_type=None, exist_ok=True)
+
+        transformer_models = self.inferencer.model.convert_to_transformers()
+
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            repo = Repository(tmp_dir, clone_from=repo_url)
+
+            self.inferencer.processor.tokenizer.save_pretrained(tmp_dir)
+
+            # convert_to_transformers (above) creates one model per prediction head.
+            # As the FarmReader models only have one head (QA) we go with this.
+            transformer_models[0].save_pretrained(tmp_dir)
+
+            large_files = []
+            for root, dirs, files in os.walk(tmp_dir):
+                for filename in files:
+                    file_path = os.path.join(root, filename)
+                    rel_path = os.path.relpath(file_path, tmp_dir)
+
+                    if os.path.getsize(file_path) > (5 * 1024 * 1024):
+                        large_files.append(rel_path)
+
+            if len(large_files) > 0:
+                logger.info("Track files with git lfs: {}".format(", ".join(large_files)))
+                repo.lfs_track(large_files)
+
+            logger.info("Push model to the hub. This might take a while")
+            commit_url = repo.push_to_hub(commit_message=commit_message)
+
+        return commit_url
+
     def predict_batch(
         self,
         queries: List[str],


Test patch: