Keras определенно является предпочтительным оружием, когда дело доходит до построения моделей глубокого обучения (с бэкэндом tensorflow). В SearchInk мы решаем самые разные задачи в области анализа документов, создавая и внедряя модели глубокого обучения. Одна из самых серьезных проблем при этом - время, необходимое для проведения каждого эксперимента. Поскольку нам необходимо проводить все больше и больше экспериментов за более короткие промежутки времени, мы решили, что сейчас самое подходящее время для начала распределенных вычислений на GPU для моделей глубокого обучения.

Мы оценивали разные варианты выполнения распределенных вычислений на GPU и наткнулись на Хоровод. Мы потратили несколько дней на оценку подхода Uber, и для нас было совершенно логичным стать одними из первых, кто начал использовать Horovod.

На что следует обратить внимание при запуске Horovod:

Убедитесь, что у вас есть keras 2.0.8, а не 2.0.9, потому что в 2.0.9 существует известная проблема, из-за которой каждый работник выделяет все графические процессоры на сервере вместо графического процессора, назначенного локальным рангом. .

Кроме того, при запуске команды mpirun используйте следующие параметры:

$ mpirun --oversubscribe --bind-to none -np <number of GPU's>
oversubscribe - Nodes are allowed to be oversubscribed, even on a managed system, and overloading of processing elements.
bind-to none - Not binding the process to any cores
np - Run this many copies of the program on the given nodes
$ man mpirun 
For more details on the mpirun command and its option

Пример с Keras Inception3:

  1. Добавление оптимизатора Horovod в модель
def create_inception_model(num_classes, input_shape, fully_trainable=False):
    '''
     A method to create inception 3 model with horovod optimizer
    :param num_classes:
    :param input_shape:
    :param fully_trainable:
    :return:
    '''

    base_model = InceptionV3(include_top=False, input_tensor=Input(input_shape),
                             weights=None, pooling='avg')
    pred_name = "predictions_{}".format(num_classes)

    x = base_model.output
    predictions = Dense(num_classes, activation='softmax', name=pred_name)(x)

    # create graph of your new model
    model = Model(input=base_model.input, output=predictions)

    for l in model.layers:
        l.trainable = fully_trainable

    # Adjust learning rate based on number of GPUs (naive approach).
    opt = keras.optimizers.Adadelta()

    # Add Horovod Distributed Optimizer.
    opt = hvd.DistributedOptimizer(opt)

    model.compile(loss='sparse_categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
    return model

2. Инициализация Horovod и добавление необходимых обратных вызовов:

#adding horovod
hvd.init()
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
config.gpu_options.visible_device_list = str(hvd.local_rank())
K.set_session(tf.Session(config=config))
file_list = build_data(data_path)
train_size = math.ceil(0.8 * len(file_list))
train_file_list = file_list[:train_size]
test_file_list=file_list[train_size:]
nbatches_train, mod = divmod(len(train_file_list),
                             BATCH_SIZE)
nbatches_valid, mod = divmod(len(test_file_list),
                             BATCH_SIZE)
model = create_inception_model(num_classes=4, 
                 input_shape=(HEIGHT, WIDTH, CHANNELS),
                              fully_trainable=True)
cb = []
if hvd.rank()  == 0:
    modelCheckPointCallBack = keras.callbacks.ModelCheckpoint('./checkpoint-{epoch}.h5')
    cb.append(keras.callbacks.TensorBoard('./logs'))
    cb.append(modelCheckPointCallBack)
cb.append(hvd.callbacks.BroadcastGlobalVariablesCallback(0))
cb.append(hvd.callbacks.MetricAverageCallback())
cb.append(hvd.callbacks.LRWarmupCallback(warmup_epochs=5, verbose=1))
cb.append(keras.callbacks.ReduceLROnPlateau(patience=3, verbose=1))
train_generator = generator_from_data_path(train_file_list,
                                      batch_size=BATCH_SIZE,
                                      width=WIDTH, 
                                      height=HEIGHT)
test_generator = generator_from_data_path(test_file_list,
                                        batch_size=BATCH_SIZE,
                                        width=WIDTH, 
                                        height=HEIGHT)
model.fit_generator(train_generator, epochs=EPOCHS,
                    steps_per_epoch=nbatches_train // hvd.size(),
                    callbacks=cb,
                    validation_data=test_generator,
                 validation_steps=3 * nbatches_valid // hvd.size(),
                    verbose=1)
serialize_mode(model, name='inception')

Вуаля! мы готовы запустить Inception3 на нескольких графических процессорах. Вы можете проверить использование графического процессора, запустив:

watch nvidia-smi

В нашем следующем блоге мы продемонстрируем распределенные вычисления на GPU с помощью TensorFlow и Horovod.

Мы хотели бы поблагодарить команду инженеров Uber за создание Horovod
проекта с открытым исходным кодом. Это определенно большой прогресс в правильном направлении.

Выводы:

Для сравнения: мы использовали архитектуру Inception3 с образцом из 18 тысяч документов на GPU Tesla K80 размером 1 * 12 ГБ.
Каждая эпоха длилась около 30 минут. С Horovod и обновленным экземпляром с 4 * 12 ГБ Tesla K80 GPU, каждая эпоха сократилась примерно до 5–6 минут.