          
          for thread in threads:
              thread.start()            
          
          while any([t.is_alive() for t in threads]):
              sleep(0.1)
              
          print('saved model weights')              
      
                             
      @property
      def gpus(self):
          return self._gpus
 
</s> remove         # thought maybe I/O bound, sometimes saving in parallel is faster
        threads = []
        t = Thread(target=self.encoder.save_weights, args=(str(self.model_dir / hdf['encoderH5']),))
        threads.append(t)         
        t = Thread(target=self.decoder_A.save_weights, args=(str(self.model_dir / hdf['decoder_AH5']),))
        threads.append(t)
        t = Thread(target=self.decoder_B.save_weights, args=(str(self.model_dir / hdf['decoder_BH5']),))
        threads.append(t)
        
        for thread in threads:
            thread.start()            
 </s> add         print('\nsaving model weights', end='', flush=True)        
        from concurrent.futures import ThreadPoolExecutor, as_completed </s> remove         return "<{}: ver={}, nn_dims={}, img_size={}>".format(self.model_name, 
 </s> add         return "<{}: ver={}, dense_dim={}, img_size={}>".format(self.model_name, 
        # thought maybe I/O bound, sometimes saving in parallel is faster
        with ThreadPoolExecutor(max_workers=4) as executor:
            futures = [executor.submit(getattr(self, mdl_name.rstrip('H5')).save_weights, str(self.model_dir / mdl_H5_fn)) for mdl_name, mdl_H5_fn in hdf.items()]
            for future in as_completed(futures):
                future.result()
                print('.', end='', flush=True)  

        print('done', flush=True)              
          
          for thread in threads:
              thread.start()            
          
        # thought maybe I/O bound, sometimes saving in parallel is faster
        with ThreadPoolExecutor(max_workers=4) as executor:
            futures = [executor.submit(getattr(self, mdl_name.rstrip('H5')).save_weights, str(self.model_dir / mdl_H5_fn)) for mdl_name, mdl_H5_fn in hdf.items()]
            for future in as_completed(futures):
                future.result()
                print('.', end='', flush=True)  

        print('done', flush=True)              
        # thought maybe I/O bound, sometimes saving in parallel is faster
        with ThreadPoolExecutor(max_workers=4) as executor:
            futures = [executor.submit(getattr(self, mdl_name.rstrip('H5')).save_weights, str(self.model_dir / mdl_H5_fn)) for mdl_name, mdl_H5_fn in hdf.items()]
            for future in as_completed(futures):
                future.result()
                print('.', end='', flush=True)  

        print('done', flush=True)              
        # thought maybe I/O bound, sometimes saving in parallel is faster
        with ThreadPoolExecutor(max_workers=4) as executor:
            futures = [executor.submit(getattr(self, mdl_name.rstrip('H5')).save_weights, str(self.model_dir / mdl_H5_fn)) for mdl_name, mdl_H5_fn in hdf.items()]
            for future in as_completed(futures):
                future.result()
                print('.', end='', flush=True)  

        print('done', flush=True)              
        # thought maybe I/O bound, sometimes saving in parallel is faster
        with ThreadPoolExecutor(max_workers=4) as executor:
            futures = [executor.submit(getattr(self, mdl_name.rstrip('H5')).save_weights, str(self.model_dir / mdl_H5_fn)) for mdl_name, mdl_H5_fn in hdf.items()]
            for future in as_completed(futures):
                future.result()
                print('.', end='', flush=True)  

        print('done', flush=True)              
      
                             
      @property
      def gpus(self):
          return self._gpus
keep keep keep keep replace replace replace replace keep keep keep keep keep
 changed saving routine to ThreadPoolExecutor (faster with Python3) 
plugins/Model_OriginalHighRes/Model.py