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

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

    callbacks = input.setup(model.get_inputs_desc())
    ps_device = ('cpu' if (len(tower) >= 4) else 'gpu')
    if (ps_device == 'gpu'):
        raw_devices = ['/gpu:{}'.format(k) for k in tower]
        devices = [LeastLoadedDeviceSetter(d, raw_devices) for d in raw_devices]
    else:
        devices = [tf.train.replica_device_setter(worker_device=d, ps_device='/cpu:0', ps_tasks=1) for d in raw_devices]
    grad_list = MultiGPUTrainerBase.build_on_multi_tower(tower, (lambda : MultiGPUTrainerBase._build_graph_get_grads(model, input)), devices)
    MultiGPUTrainerBase._check_grad_list(grad_list)
    if (scale_gradient and (len(tower) > 1)):
        gradproc = ScaleGradient(('.*', (1.0 / len(tower))), verbose=False)
        grad_list = [gradproc.process(gv) for gv in grad_list]
    train_ops = []
    opt = model.get_optimizer()
    with tf.name_scope('async_apply_gradients'):
        for (i, grad_and_vars) in enumerate(zip(*grad_list)):
            v = grad_and_vars[0][1]
            with tf.device(v.device):
                train_ops.append(opt.apply_gradients(grad_and_vars, name='apply_grad_{}'.format(i)))
        return (tf.group(name='train_op', *train_ops), callbacks)
