Repository: facebookresearch/ParlAI

Issue:
controllable_seq2seq.py fails lint.
https://circleci.com/gh/facebookresearch/ParlAI/7716?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link

```
#!/bin/bash -eo pipefail
bash ./tests/lint_changed.sh
projects/controllable_dialogue/controllable_seq2seq/controllable_seq2seq.py:810:21: B006 Do not use mutable data structures for argument defaults. All calls reuse one instance of that data structure, persisting changes between them.
        wd_features=[],
                    ^
projects/controllable_dialogue/controllable_seq2seq/controllable_seq2seq.py:811:16: B006 Do not use mutable data structures for argument defaults. All calls reuse one instance of that data structure, persisting changes between them.
        wd_wts=[],
               ^
Exited with code 1
```



Discussion:
Issue arose in #1826.

Resolving patch:
diff --git a/projects/controllable_dialogue/controllable_seq2seq/controllable_seq2seq.py b/projects/controllable_dialogue/controllable_seq2seq/controllable_seq2seq.py
--- a/projects/controllable_dialogue/controllable_seq2seq/controllable_seq2seq.py
+++ b/projects/controllable_dialogue/controllable_seq2seq/controllable_seq2seq.py
@@ -48,8 +48,7 @@
 
 class ControllableSeq2seqAgent(TorchAgent):
     """
-    This is a version of the Seq2seqAgent, that allows for attribute control via
-    Conditional Training (CT) and/or Weighted Decoding (WD).
+    Seq2Seq withattribute control via Conditional Training and/or Weighted Decoding.
 
     See the paper:
     "What makes a good conversation? How controllable attributes affect human judgments"
@@ -439,6 +438,8 @@ def _init_model(self, states=None):
 
     def _init_controls(self):
         """
+        Initialize controls.
+
         Sets the following:
 
           self.control_vars: list of strings. The CT controls sorted alphabetically.
@@ -783,6 +784,7 @@ def _pick_cands(self, cand_preds, cand_inds, cands):
         return cand_replies
 
     def greedy_search(self, batch):
+        """Perform greedy search."""
         cand_params = self._build_cands(batch)
         seq_len = None if not self.multigpu else batch.text_vec.size(1)
         out = self.model(
@@ -807,35 +809,43 @@ def beam_search(
         min_n_best=5,
         max_ts=40,
         block_ngram=0,
-        wd_features=[],
-        wd_wts=[],
+        wd_features=None,
+        wd_wts=None,
     ):
-        """ Beam search given the model and Batch
+        """
+        Beam search given the model and Batch.
+
         This function uses model with the following reqs:
+
         - model.encoder takes input returns tuple (enc_out, enc_hidden, attn_mask)
         - model.decoder takes decoder params and returns decoder outputs after attn
         - model.output takes decoder outputs and returns distr over dictionary
 
-        Function arguments:
-        model : nn.Module, here defined in modules.py
-        batch : Batch structure with input and labels
-        beam_size : Size of each beam during the search
-        start : start of sequence token
-        end : end of sequence token
-        pad : padding token
-        min_length : minimum length of the decoded sequence
-        min_n_best : minimum number of completed hypothesis generated from each beam
-        max_ts: the maximum length of the decoded sequence
-        wd_features: list of strings, the WD features to use
-        wd_weights: list of floats, the WD weights to use
-
-        Return:
-        beam_preds_scores : list of tuples (prediction, score) for each sample in Batch
-        n_best_preds_scores : list of n_best list of tuples (prediction, score) for
-                              each sample from Batch
-        beams : list of Beam instances defined in Beam class, can be used for any
-                following postprocessing, e.g. dot logging.
+        :param model: nn.Module, here defined in modules.py
+        :param batch: Batch structure with input and labels
+        :param beam_size: Size of each beam during the search
+        :param start: start of sequence token
+        :param end: end of sequence token
+        :param pad: padding token
+        :param min_length: minimum length of the decoded sequence
+        :param min_n_best: minimum number of completed hypothesis generated
+            from each beam
+        :param max_ts: the maximum length of the decoded sequence
+        :param wd_features: list of strings, the WD features to use
+        :param wd_weights: list of floats, the WD weights to use
+
+        :return:
+            - beam_preds_scores : list of tuples (prediction, score) for each
+              sample in Batch
+            - n_best_preds_scores : list of n_best list of tuples (prediction,
+              score) for each sample from Batch
+            - beams : list of Beam instances defined in Beam class, can be used
+              for any following postprocessing, e.g. dot logging.
         """
+        if wd_features is None:
+            wd_features = []
+        if wd_wts is None:
+            wd_wts = []
         encoder_states = model.encoder(batch.text_vec)
         enc_out = encoder_states[0]
         enc_hidden = encoder_states[1]
@@ -995,6 +1005,7 @@ def beam_search(
         return beam_preds_scores, n_best_beam_preds_scores, beams
 
     def extend_input(self, batch):
+        """Extend the input."""
         # add pad tensor to text vec
         pad_tensor = torch.zeros(1, batch.text_vec.size(1)).long().cuda()
         text_vec = torch.cat([batch.text_vec, pad_tensor], 0)
@@ -1016,6 +1027,7 @@ def extend_input(self, batch):
         return batch
 
     def truncate_input(self, batch):
+        """Truncate the input."""
         # truncate batch for multigpu
         text_vec = batch.text_vec[:-1]
         batch = batch._replace(text_vec=text_vec)
@@ -1025,6 +1037,7 @@ def truncate_input(self, batch):
         return batch
 
     def truncate_output(self, out):
+        """Truncate the output."""
         new_out_0 = out[0][:-1]
         new_out_1 = None if out[1] is None else out[1][:-1]
         new_out_2 = [vec[:-1] for vec in out[2]]
diff --git a/projects/controllable_dialogue/controllable_seq2seq/modules.py b/projects/controllable_dialogue/controllable_seq2seq/modules.py
--- a/projects/controllable_dialogue/controllable_seq2seq/modules.py
+++ b/projects/controllable_dialogue/controllable_seq2seq/modules.py
@@ -4,10 +4,7 @@
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
 
-"""
-This file is derived from parlai/core/seq2seq/modules.py, and adapted to handle
-CT controlled models.
-"""
+"""Seq2seq module adapted to handle CT controlled models."""
 
 import math
 import torch
@@ -91,12 +88,16 @@ def __init__(
         unknown_idx=3,
         input_dropout=0,
         longest_label=1,
-        control_settings={},
+        control_settings=None,
     ):
-        """Initialize seq2seq model.
+        """
+        Initialize seq2seq model.
 
         See cmdline args in Seq2seqAgent for description of arguments.
         """
+        if control_settings is None:
+            control_settings = {}
+
         super().__init__()
         self.attn_type = attention
 
@@ -306,32 +307,39 @@ def forward(
         maxlen=None,
         seq_len=None,
     ):
-        """Get output predictions from the model.
-
-        :param xs:          (bsz x seqlen) LongTensor input to the encoder
-        :param ys:          expected output from the decoder. used for teacher
-                            forcing to calculate loss.
-        :param cands:       set of candidates to rank
-        :param prev_enc:    if you know you'll pass in the same xs multiple
-                            times, you can pass in the encoder output from the
-                            last forward pass to skip recalcuating the same
-                            encoder output.
-        :param maxlen:      max number of tokens to decode. if not set, will
-                            use the length of the longest label this model
-                            has seen. ignored when ys is not None.
-        :param seq_len      this is the sequence length of the input (xs), i.e.
-                            xs.size(1). we use this to recover the proper
-                            output sizes in the case when we distribute over
-                            multiple gpus
-        :param ctrl_inputs: (bsz x num_controls) LongTensor containing control vars
-
-        :returns: scores, candidate scores, and encoder states
-            scores contains the model's predicted token scores.
-                (bsz x seqlen x num_features)
-            candidate scores are the score the model assigned to each candidate
-                (bsz x num_cands)
-            encoder states are the (output, hidden, attn_mask) states from the
-                encoder. feed this back in to skip encoding on the next call.
+        """
+        Get output predictions from the model.
+
+        :param xs:
+            (bsz x seqlen) LongTensor input to the encoder
+        :param ys:
+            expected output from the decoder. used for teacher forcing
+            to calculate loss.
+        :param cands:
+            set of candidates to rank
+        :param prev_enc:
+            if you know you'll pass in the same xs multiple times, you can pass
+            in the encoder output from the last forward pass to skip
+            recalcuating the same encoder output.
+        :param maxlen:
+            max number of tokens to decode. if not set, will use the length of
+            the longest label this model has seen. ignored when ys is not None.
+        :param seq_len:
+            this is the sequence length of the input (xs), i.e. xs.size(1). we
+            use this to recover the proper output sizes in the case when we
+            distribute over multiple gpus
+        :param ctrl_inputs:
+            (bsz x num_controls) LongTensor containing control vars
+
+        :returns:
+            scores, candidate scores, and encoder states
+
+            - scores contains the model's predicted token scores.
+              (bsz x seqlen x num_features)
+            - candidate scores are the score the model assigned to each candidate
+              (bsz x num_cands)
+            - encoder states are the (output, hidden, attn_mask) states from the
+              encoder. feed this back in to skip encoding on the next call.
         """
         if ys is not None:
             # keep track of longest label we've ever seen
@@ -371,9 +379,7 @@ def forward(
 
 
 class ControlEncoder(nn.Module):
-    """
-    Given CT control variable inputs, gives the concatenated control embeddings vector.
-    """
+    """Given CT control variable, gives the concatenated control embeddings vector."""
 
     def __init__(self, control_settings):
         super().__init__()
@@ -394,13 +400,13 @@ def __init__(self, control_settings):
 
     def forward(self, control_inputs):
         """
-        Inputs:
-            :param control_inputs: (bsz x num_control_vars) LongTensor of control
-              variable values (i.e. bucket ids)
+        Forward pass.
+
+        :param control_inputs: (bsz x num_control_vars) LongTensor of control
+            variable values (i.e. bucket ids)
 
-        Outputs:
-            :returns control_embs: (bsz x sum of control emb sizes) FloatTensor of
-              control variable embeddings, concatenated
+        :returns: control_embs, (bsz x sum of control emb sizes) FloatTensor of
+            control variable embeddings, concatenated
         """
         # list length num_control_vars of tensors shape (bsz, 1)
         control_inputs = torch.split(control_inputs, 1, dim=1)
@@ -558,9 +564,11 @@ def __init__(
         attn_time='pre',
         attn_length=-1,
         sparse=False,
-        control_settings={},
+        control_settings=None,
     ):
         """Initialize recurrent decoder."""
+        if control_settings is None:
+            control_settings = {}
         super().__init__()
         self.dropout = nn.Dropout(p=dropout)
         self.layers = numlayers
@@ -877,7 +885,7 @@ def forward(self, xes, hidden, attn_params):
             # calculate activation scores, apply mask if needed
             if attn_mask is not None:
                 # remove activation from NULL symbols
-                attn_w_premask.masked_fill_((1 - attn_mask), -NEAR_INF)
+                attn_w_premask.masked_fill_(~attn_mask, -NEAR_INF)
             attn_weights = F.softmax(attn_w_premask, dim=1)
 
         # apply the attention weights to the encoder states
diff --git a/projects/wizard_of_wikipedia/generator/agents.py b/projects/wizard_of_wikipedia/generator/agents.py
--- a/projects/wizard_of_wikipedia/generator/agents.py
+++ b/projects/wizard_of_wikipedia/generator/agents.py
@@ -109,7 +109,9 @@ def __init__(self, opt, shared=None):
     def _dummy_batch(self, bsz, maxlen):
         batch = super()._dummy_batch(bsz, maxlen)
         batch['know_vec'] = th.zeros(bsz, 2, 2).long().cuda()
-        batch['ck_mask'] = th.ones(bsz, 2).byte().cuda()
+        # bool/uint8 backwards for pytorch 1.0/1.2 compatibility
+        ck_mask = (th.ones(bsz, 2, dtype=th.uint8) != 0).cuda()
+        batch['ck_mask'] = ck_mask
         batch['cs_ids'] = th.zeros(bsz).long().cuda()
         batch['use_cs_ids'] = True
         return batch
@@ -253,9 +255,10 @@ def batchify(self, obs_batch):
         # knowledge mask is a N x K tensor saying which items we're allowed to
         # attend over
         bsz = len(reordered_observations)
-        ck_mask = th.zeros(bsz, K).byte()
+        ck_mask = th.zeros(bsz, K, dtype=th.uint8)
         for i, klen in enumerate(knowledge_counts):
             ck_mask[i, :klen] = 1
+        ck_mask = ck_mask != 0  # for pytorch 1.0/1.2 uint8/bool compatibility
         # and the correct labels
         cs_ids = th.LongTensor(bsz).zero_()
 


Test patch: