def build(self, grad_list, get_opt_fn):
        """
        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
        """
        assert len(grad_list) == len(self.towers)
        DataParallelBuilder._check_grad_list(grad_list)

        # debug tower performance:
        # 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_colocate(grad_list)
        # debug tower performance:
        # 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

        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
        
tensorpack/graph_builder/training.py