Repository: facebookresearch/ParlAI

Issue:
Programmatic usage of model zoo requires overriding opts
**Bug description**

Instead of running the pretrained models in interactive mode as [prescribed in the docs](https://parl.ai/docs/zoo.html), I am trying to programmatically load the model responses in my script, for "Seq2Seq" and "PolyEncoder" models for Personachat dataset. For both of these cases, the agent fails to load.

**Reproducing**

A simple example would be:

```
def setup_args(parser=None):
    if parser is None:
        parser = ParlaiParser(True, True, 'Display data from a task')
    parser.add_pytorch_datateacher_args()
    return parser

def load_dialogs(model, mode='test'):
  parser = setup_args()
  parlai_opts = []
  if mode == 'train':
      mode = 'train:ordered'
  parlai_opts.append("-dt {}".format(mode))
  if model == "seq2seq":
    parlai_opts.append("-mf zoo:convai2/seq2seq/convai2_self_seq2seq_model")
    parlai_opts.append("-m legacy:seq2seq:0")
  elif model == "polyencoder":
    parlai_opts.append("-mf zoo:pretrained_transformers/model_poly/model")
  else:
    return
  # combine opts
  opt = parser.parse_args(" ".join(parlai_opts).split(' '))
  agent = create_agent(opt, requireModelExists=True)
  ...
```

**Possible Solution**

After much hacking, I figured adding `override` opts after the parsing solves the issue. For Seq2Seq, the default opts have a hard time figuring out the proper model version (even when specified in `-mf` flag), thus explicitly setting it works (had to set both model and model_file):

```
opt = parser.parse_args(" ".join(parlai_opts).split(' '))
# for seq2seq
opt['override'] = {
    'model': 'legacy:seq2seq:0',
    'model_file': '/private/home/koustuvs/mlp/parlai_koustuvs/data/models/convai2/seq2seq/convai2_self_seq2seq_model'
}
```

For PolyEncoder, the opt `fixed_candidates_path` turns out to start only from `data/...` without the base folder prepended: 

```
# for PolyEncoder
opt['override'] = {
    'fixed_candidates_path':"/private/home/koustuvs/mlp/parlai_koustuvs/data/models/pretrained_transformers/convai_trainset_cands.txt"
}
```

Hope this helps in zeroing in the issue.





Discussion:
Also, using PolyEncoder pretrained model requires [Apex](https://github.com/NVIDIA/apex) to be installed (or rather compiled), and `no_cuda` doesn't work, it needs gpu to run.
Thanks for the nice test case. I'm a bit surprised; the parse_args() should be handling these for you.

As far as polyencoder requiring Apex, that should really be another issue, but we are aware of it. @emilydinan actually made a script to convert it to a CPU friendly model recently. It sounds like it's appropriate to move it to the public repo.

Resolving patch:
diff --git a/parlai/core/params.py b/parlai/core/params.py
--- a/parlai/core/params.py
+++ b/parlai/core/params.py
@@ -20,6 +20,8 @@
 from parlai.tasks.tasks import ids_to_tasks
 from parlai.core.utils import Opt, load_opt_file
 
+from typing import List, Optional
+
 
 def print_git_commit():
     """Print the current git commit of ParlAI and parlai_internal."""
@@ -228,7 +230,6 @@ def __init__(
         self.add_arg = self.add_argument
 
         # remember which args were specified on the command line
-        self.cli_args = _sys.argv[1:]
         self.overridable = {}
 
         if add_parlai_args:
@@ -903,7 +904,7 @@ def _infer_datapath(self, opt):
 
         return opt
 
-    def _process_args_to_opts(self):
+    def _process_args_to_opts(self, args_that_override: Optional[List[str]] = None):
         self.opt = Opt(vars(self.args))
 
         # custom post-parsing
@@ -924,14 +925,20 @@ def _process_args_to_opts(self):
                         elif '_StoreFalseAction' in str(type(a)):
                             store_false.append(option)
 
-        for i in range(len(self.cli_args)):
-            if self.cli_args[i] in option_strings_dict:
-                if self.cli_args[i] in store_true:
-                    self.overridable[option_strings_dict[self.cli_args[i]]] = True
-                elif self.cli_args[i] in store_false:
-                    self.overridable[option_strings_dict[self.cli_args[i]]] = False
-                elif i < len(self.cli_args) - 1 and self.cli_args[i + 1][:1] != '-':
-                    key = option_strings_dict[self.cli_args[i]]
+        if args_that_override is None:
+            args_that_override = _sys.argv[1:]
+
+        for i in range(len(args_that_override)):
+            if args_that_override[i] in option_strings_dict:
+                if args_that_override[i] in store_true:
+                    self.overridable[option_strings_dict[args_that_override[i]]] = True
+                elif args_that_override[i] in store_false:
+                    self.overridable[option_strings_dict[args_that_override[i]]] = False
+                elif (
+                    i < len(args_that_override) - 1
+                    and args_that_override[i + 1][:1] != '-'
+                ):
+                    key = option_strings_dict[args_that_override[i]]
                     self.overridable[key] = self.opt[key]
         self.opt['override'] = self.overridable
 
@@ -971,7 +978,7 @@ def parse_and_process_known_args(self, args=None):
         line arguments that parlai doesn't know what to do with.
         """
         self.args, unknowns = super().parse_known_args(args=args)
-        self._process_args_to_opts()
+        self._process_args_to_opts(args)
         return self.opt, unknowns
 
     def parse_args(self, args=None, namespace=None, print_args=True):
@@ -985,7 +992,7 @@ def parse_args(self, args=None, namespace=None, print_args=True):
         self.add_extra_args(args)
         self.args = super().parse_args(args=args)
 
-        self._process_args_to_opts()
+        self._process_args_to_opts(args)
 
         if print_args:
             self.print_args()


Test patch: