code: def build(self, grad_list, get_opt_fn):
          
          assert len(grad_list) == len(self.towers)
          DataParallelBuilder._check_grad_list(grad_list)
  
          if self._scale_gradient and len(self.towers) > 1:
              # pretend to average the grads, in order to make async and
              # sync have consistent effective learning rate
              gradproc = ScaleGradient(('.*', 1.0 / len(self.towers)), verbose=False)
              grad_list = [gradproc.process(gv) for gv in grad_list]
          # Ngpu x Nvar x 2
  
          train_ops = []
          opt = get_opt_fn()
          with tf.name_scope('async_apply_gradients'):
              for i, grad_and_vars in enumerate(zip(*grad_list)):
                  # Ngpu x 2
                  v = grad_and_vars[0][1]
                  with tf.device(v.device):
                      # will call apply_gradients (therefore gradproc) multiple times
                      train_ops.append(opt.apply_gradients(
                          grad_and_vars, name='apply_grad_{}'.format(i)))
          return tf.group(*train_ops, name='train_op')
docs: Args:
              grad_list ([[(grad, var), ...], ...]): #GPU lists to be reduced. Each is the gradients computed on each GPU.
              get_opt_fn (-> tf.train.Optimizer): callable which returns an optimizer
  
          Returns:
              tf.Operation: the training op