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.