Pytorch: вычисление времени работы на GPU и CPU цикла for

#machine-learning #deep-learning #neural-network #pytorch

#машинное обучение #глубокое обучение #нейронная сеть #pytorch

Вопрос:

Я действительно новичок в pytorch. И я действительно запутался весь день, пока пытался выяснить, почему мой nn работает медленнее на GPU, чем на CPU. Я не понимаю, когда я рассчитывал время выполнения с помощью time.time(), время всего цикла сильно отличается от суммы каждого отдельного времени выполнения. Вот часть моего кода. Кто-нибудь может мне помочь? Ценю это!

     time_out = 0
    time_in = 0

    for epoch in tqdm(range(self.n_epoch)):

        running_loss = 0
        running_error = 0
        running_acc = 0

        if self.cuda:
            torch.cuda.synchronize()                #time_out_start
        epst1 = time.time()


        for step, (batch_x, batch_y) in enumerate(self.normal_loader):

            if self.cuda:
                torch.cuda.synchronize()                        #time_in_start
            t1 = time.time()

            batch_x, batch_y = batch_x.to(self.device), batch_y.to(self.device)

            b_x = Variable(batch_x)
            b_y = Variable(batch_y)
            
            pred_y = self.model(b_x)
            #print (pred_y)
            
            loss = self.criterion(pred_y, b_y)

            error = mae(pred_y.detach().cpu().numpy(),b_y.detach().cpu().numpy())
            acc = r2(b_y.detach().cpu().numpy(),pred_y.detach().cpu().numpy())

            #print (loss)
            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()

            running_acc  = acc
            running_loss  = loss.item()
            running_error  = error

            if self.cuda:
                torch.cuda.synchronize()                        #time_in_end
            t6 = time.time()

            time_in  = t6-t1

        if self.cuda:
            torch.cuda.synchronize()                    #time_out_end         
        eped1 = time.time()

        time_out  = eped1-epst1

    print ('loop time(out)',time_out)
    print ('loop time(in)',time_in)
 

Результат:
ПРОЦЕССОР:
ЭПОХА 10: выход: 1.283 с, вход: 0.695 с
ЭПОХА 50: выход: 6,43 с, вход: 3,288 с
ЭПОХА 100: выход: 12,646 с, вход: 6,386 с
Графический процессор:
ЭПОХА 10: выход: 3,92 с, вход: 1,471 с
ЭПОХА 50: выход: 9,35 с, вход: 3,04 с
ЭПОХА 100: выход: 18,418 с, вход: 5,655

Я понимаю, что передача данных с cpu на gpu требует некоторого времени. Таким образом, по мере увеличения эпох время вычисления GPU должно становиться меньше времени процессора. Мой вопрос:

  1. почему время, которое я записываю вне цикла, так отличается от внутреннего? Есть ли какой-либо шаг, который я пропустил, чтобы записать время выполнения?
  2. И почему GPU стоит больше времени вне времени, даже если время внутри меньше времени процессора?

Сеть действительно проста, а именно:

 class Model(nn.Module):
def __init__(self,n_input,n_nodes1,n_nodes2):
    super(Model, self).__init__()

    self.n_input = n_input
    self.n_nodes1 = n_nodes1
    self.n_nodes2 = n_nodes2

    self.l1 = nn.Linear(self.n_input, self.n_nodes1)
    self.l2 = nn.Linear(self.n_nodes1, self.n_nodes2)
    self.l3 = nn.Linear(self.n_nodes2, 1)

def forward(self,x):

    h1 = F.relu(self.l1(x))
    h2 = F.relu(self.l2(h1))
    h = self.l3(h2)

    return h
 

обучающие данные формируются следующим образом: (задача регрессии, input_x — это дескрипторы, а y — целевое значение)

 def load_train_normal(self,x,y,batch_size = 100):       
    if batch_size:
        self.batch_size = batch_size

    
    self.x_train_n, self.y_train_n = Variable(torch.from_numpy(x).float()), Variable(torch.from_numpy(y).float())
    
    #x, y = Variable(torch.from_numpy(x).float()), Variable(torch.from_numpy(y).float())
    self.dataset = Data.TensorDataset(self.x_train_n,self.y_train_n)
    self.normal_loader = Data.DataLoader(
                        dataset = self.dataset,
                        batch_size = self.batch_size,
                        shuffle = True, num_workers=2,)
 

Комментарии:

1. Лучше приведите полный пример, чтобы мы могли воспроизвести проблему. Время выполнения иногда может зависеть от размера ваших данных, что не отражается в вашем коде.

Ответ №1:

почему время, которое я записываю вне цикла, так отличается от внутреннего? Есть ли какой-либо шаг, который я пропустил, чтобы записать время выполнения?

self.normal_loader это не просто простой словарь, вектор или что-то такое простое. Итерация по нему занимает значительное количество времени.

И почему GPU стоит больше времени вне времени, даже если время внутри меньше времени процессора?

torch.cuda.synchronize() это тяжелая операция. Даже когда он даже не делал ничего полезного, например, в этом случае, поскольку pred_y.detach().cpu() уже была принудительная синхронизация.


Как получить их быстрее? Отбросьте synchronize() вызовы, они не принесут вам никакой пользы.

А затем отложите обработку pred_y до более позднего времени. Намного позже. Вы хотите вызвать модель как минимум 2 или 3 раза, прежде чем запускать первую загрузку результатов. Чем проще модель и чем меньше данных, тем больше итераций вам придется ждать.

Поскольку передача данных на GPU и обратно не просто «занимает время», они подразумевают синхронизацию. Без синхронизации модель выполнения на GPU в основном «отстает», при этом загрузка данных на GPU уже выполняется асинхронно за кулисами, а фактическое выполнение ставится в очередь только за ними. Если вы случайно или явно не синхронизируете, рабочие нагрузки начинают перекрываться, материал (загрузка, выполнение, работа процессора) начинает выполняться параллельно. Ваше эффективное время выполнения приближается max(upload, download, GPU execution, CPU execution) .

При синхронизации нет задач для перекрытия и нет пакетов для формирования из однотипных задач. Загрузка, выполнение, загрузка, часть процессора, все это происходит последовательно. Ваше время выполнения заканчивается upload download GPU execution CPU execution . Некоторые дополнительные накладные расходы для прерывания пакетной обработки на уровне драйвера сверху. Так легко в 5-10 раз медленнее, чем должно быть.

Комментарии:

1. Привет, Ext3h, спасибо за ваше отличное объяснение. Я понял это. Большое спасибо!