'Args:
model (ModelDesc):
input (InputSource):
tower (list[int]):
Returns:
tf.Operation: the training op
[Callback]: the callbacks to be added'

@staticmethod
def setup_graph(model, input, tower):

    callbacks = input.setup(model.get_inputs_desc())
    raw_devices = ['/gpu:{}'.format(k) for k in tower]
    grad_list = MultiGPUTrainerBase.build_on_multi_tower(tower, (lambda : MultiGPUTrainerBase._build_graph_get_grads(model, input)), use_vs=([False] + ([True] * (len(tower) - 1))))
    grads = SyncMultiGPUTrainerReplicated._allreduce_grads(grad_list)
    train_ops = []
    opt = model.get_optimizer()
    for idx in range(len(tower)):
        with tf.device(raw_devices[idx]):
            grad_and_vars = [x[idx] for x in grads]
            train_ops.append(opt.apply_gradients(grad_and_vars, name='apply_grad_{}'.format(idx)))
    train_op = tf.group(name='train_op', *train_ops)
    cb = RunOp(SyncMultiGPUTrainerReplicated.get_post_init_ops, run_before=True, run_as_trigger=True, verbose=True)
    return (train_op, (callbacks + [cb]))
