docs: def search(
          self,
          search_space,
          valid_data,
          init_args=[],
          train_args=[],
          init_kwargs={},
          train_kwargs={},
          module_args={},
          module_kwargs={},
          max_search=None,
          shuffle=True,
          verbose=True,
          **score_kwargs,
      ):
          """
          Args:
              search_space: see config_generator() documentation
              valid_data: a tuple of Tensors (X,Y), a Dataset, or a DataLoader of
                  X (data) and Y (labels) for the dev split
              init_args: (list) positional args for initializing the model
              train_args: (list) positional args for training the model
              init_kwargs: (dict) keyword args for initializing the model
              train_kwargs: (dict) keyword args for training the model
              module_args: (dict) Dictionary of lists of module args
              module_kwargs: (dict) Dictionary of dictionaries of module kwargs
              max_search: see config_generator() documentation
              shuffle: see config_generator() documentation
          Returns:
              best_model: the highest performing trained model
  
          Note: Initialization is performed by ModelTuner instead of passing a
          pre-initialized model so that tuning may be performed over all model
          parameters, including the network architecture (which is defined before
          the train loop).
          """
          raise NotImplementedError()