code: def build(self, grad_list, get_opt_fn):
          
          assert len(grad_list) == len(self.towers)
          DataParallelBuilder._check_grad_list(grad_list)
  
          # debug tower performance (without update):
          # ops = [k[0] for k in grad_list[1]] + [k[0] for k in grad_list[0]]
          # self.train_op = tf.group(*ops)
          # return
  
          self.grads = aggregate_grads(grad_list, colocation=True)
          # grads = grad_list[0]
  
          opt = get_opt_fn()
          if self.ps_device == 'cpu':
              with tf.device('/cpu:0'):
                  train_op = opt.apply_gradients(self.grads, name='train_op')
          else:
              train_op = opt.apply_gradients(self.grads, name='train_op')
          return train_op
docs: Reduce the gradients, apply them with the optimizer,
          and set self.grads to a list of (g, v), containing the averaged gradients.
  
          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