Como usar várias GPUs para treinar modelos de Machine Learning

Os preços das GPUs despencaram no segundo trimestre de 2022 e muita gente está aproveitando para atualizar suas máquinas para “jogar”, digo, desenvolver e treinar modelos de Machine Learning.

No entanto, se você é uma pessoa que, como eu, tem um apego aos materiais que não usa mais e não consegue ver um novo destino para peças antigas, esse post é para você. Você verá como utilizar diferentes GPUs para treinar seus modelos de Machine Learning.

Este post é uma adaptação da publicação trazida por Rahul Agarwal, onde ele utiliza duas placas de vídeo iguais para treinar um modelo baseado em árvores, XGBoost, e apresenta resultados muito interessantes.

Dessa forma, esse post é uma tradução livre da seguinte publicação, adaptado para duas GPU’s diferentes:

https://towardsdatascience.com/lightning-fast-xgboost-on-multiple-gpus-32710815c7c3

No entanto, não tenho duas placas iguais e me perguntei: Será que consigo usar a mesma metodologia para duas placas diferentes e obter resultados similares?

Preparando o Ambiente

Para rodar o notebook que vou apresentar aqui, utilizei as seguintes configurações:

  • Sistema operacional: Ubuntu 20.04
  • Cuda Toolkit 11.05
  • Python 3.9 → Criado a partir do snipset:
conda create -n rapids-22.06 -c rapidsai -c nvidia -c conda-forge  \\
    rapids=22.06 python=3.9 cudatoolkit=11.5
  • GPU 0 : RTX 3070
  • GPU 1 : GTX 1080
  • Processor : Ryzen 5 3600x

Libraries

import numpy as np
import os.path
import pandas
import time
import xgboost as xgb
import sys
from dask.distributed import Client, wait
from dask_cuda import LocalCUDACluster
import dask
%matplotlib inline

Downloading the Data

Vamos utilziar os dados do UCI Higgs. É um problema de classificação binária com onze milhões de linhas e 29 colunas e pode levar um tempo considerável para ser resolvido. Para carregá-lo, utilizei a função abaixo:

if sys.version_info[0] >= 3:
    from urllib.request import urlretrieve
else:
    from urllib import urlretrieve

data_url = "<https://archive.ics.uci.edu/ml/machine-learning-databases/00280/HIGGS.csv.gz>"
dmatrix_train_filename = "higgs_train.dmatrix"
dmatrix_test_filename = "higgs_test.dmatrix"
csv_filename = "HIGGS.csv.gz"
train_rows = 10500000
test_rows = 500000
num_round = 1000

plot = True

# return xgboost dmatrix
def load_higgs():
    if os.path.isfile(dmatrix_train_filename) and os.path.isfile(dmatrix_test_filename):           
        dtrain = xgb.DMatrix(dmatrix_train_filename)
        dtest = xgb.DMatrix(dmatrix_test_filename)
        if dtrain.num_row() == train_rows and dtest.num_row() == test_rows:
            print("Loading cached dmatrix...")
            return dtrain, dtest

    if not os.path.isfile(csv_filename):
        print("Downloading higgs file...")
        urlretrieve(data_url, csv_filename)

    df_higgs_train = pandas.read_csv(csv_filename, dtype=np.float32, 
                                     nrows=train_rows, header=None)
    dtrain = xgb.DMatrix(df_higgs_train.loc[:, 1:29], df_higgs_train[0])
    dtrain.save_binary(dmatrix_train_filename)
    df_higgs_test = pandas.read_csv(csv_filename, dtype=np.float32, 
                                    skiprows=train_rows, nrows=test_rows, 
                                    header=None)
    dtest = xgb.DMatrix(df_higgs_test.loc[:, 1:29], df_higgs_test[0])
    dtest.save_binary(dmatrix_test_filename)

    return dtrain, dtest
dtrain, dtest = load_higgs()

XGboost com uma GPU

Depois te termos nossos dados carregados e separados entre treino e teste, podemos treinar um xgboost. Primeiro, utilizaremos somente a CPU. No meu caso, um Ryzen 5 3600x e monitoramos o tempo gasto :

print("Training with CPU ...")
param = {
    'objective':'binary:logistic',
    'eval_metric':'error',
    'silent':1,
    'tree_method':'hist'
}

tmp = time.time()
cpu_res = {}
xgb.train(param, dtrain, num_round, evals=[(dtest, "test")], 
          evals_result=cpu_res)
cpu_time = time.time() - tmp
print("CPU Training Time: %s seconds" % (str(cpu_time)))

O tempo gasto usando somente a CPU foi de , aproximadamente, ~450 segundos. Ou seja, por volta de sete minutos.

Xgboost com uma GPU

Agora, vamos treinar nosso modelo usando uma GPU. Para isso mudamos o parâmetro tree_method para gpu_hist.

print("Training with Single GPU ...")
param = {
    'objective':'binary:logistic',
    'eval_metric':'error',
    'silent':1,
    'tree_method':'gpu_hist'
}

tmp = time.time()
gpu_res = {}
xgb.train(param, dtrain, num_round, evals=[(dtest, "test")], 
          evals_result=gpu_res)
gpu_time = time.time() - tmp
print("GPU Training Time: %s seconds" % (str(gpu_time)))

O tempo gasto rodando com uma GPU foi de, aproximadamente, 46 segundos. WOW! Uma fração do tempo!

XGBoost com várias GPUs

Agora, para gerenciar as diversas GPU’s, utilizamos uma biblioteca chamada Dask, responsável por alocar recursos de forma automática, identificando as placas de vídeos disponíveis e distribuindo as tarefas entre elas.

Primeiro, vamos preparar os dados para o cluster:

import dask.distributed

def load_higgs_for_dask(client):
    # 1. read the CSV File using Pandas
    df_higgs_train = pandas.read_csv(csv_filename, dtype=np.float32, 
                                     nrows=train_rows, header=None).loc[:, 0:30]
    df_higgs_test = pandas.read_csv(csv_filename, dtype=np.float32, 
                                    skiprows=train_rows, nrows=test_rows, 
                                    header=None).loc[:, 0:30]

    # 2. Create a Dask Dataframe from Pandas Dataframe.
    ddf_higgs_train = dask.dataframe.from_pandas(df_higgs_train, npartitions=8)
    ddf_higgs_test = dask.dataframe.from_pandas(df_higgs_test, npartitions=8)
    ddf_y_train = ddf_higgs_train[0]
    del ddf_higgs_train[0]
    ddf_y_test = ddf_higgs_test[0]
    del ddf_higgs_test[0]
    
    #3. Create Dask DMatrix Object using dask dataframes
    ddtrain = xgb.dask.DaskDMatrix(client, ddf_higgs_train ,ddf_y_train)
    ddtest = xgb.dask.DaskDMatrix(client, ddf_higgs_test ,ddf_y_test)
    
    return ddtrain, ddtest
cluster = LocalCUDACluster()
client = Client(cluster)
ddtrain, ddtest = load_higgs_for_dask(client)

Após, para treinar o modelo, acrescentamos um parâmetro ao dicionário de parâmetros:

param = {
    'objective':'binary:logistic',
    'eval_metric':'error',
    'silent':1,
    'tree_method':'hist',
    'nthread':1
}
print("Training with Multiple GPUs ...")
tmp = time.time()
output = xgb.dask.train(client, param, ddtrain, num_boost_round=1000, evals=[(ddtest, 'test')])
multigpu_time = time.time() - tmp
print("Multi GPU Training Time: %s seconds" % (str(multigpu_time)))

Com as diferentes GPU’s, obtivemos um resultado de 61 segundos. Melhor do que treinar com a CPU, mas não melhor do que treinar com somente a 3070.

Nesse sentido, não conseguimos obter os mesmos resultados que o Rahul, mas foi interessante saber que agora posso me desfazer da minha placa antiga.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *