CUDA does not support fork: See PyTorch documentation (https://pytorch.org/docs/stable/notes/multiprocessing.html#cuda-in-multiprocessing). Thus, if we initialize the cuda before calling the autogluon scheduler, we will see an error: CUDA: initialization error
pip install -U --pre "mxnet_cu100>=1.7.0b20200713, <2.0.0" -f https://sxjscience.github.io/KDD2020/
import numpy as np
import autogluon as ag
import mxnet as mx
from mxnet.gluon import nn, Trainer
from mxnet.util import use_np
@use_np
class Net:
def train_fn(self, args, reporter):
gpu_ctx_l = [mx.gpu(i) for i in range(mx.context.num_gpus())]
print('num_gpus:', len(gpu_ctx_l))
np.random.seed(123)
mx.random.seed(123)
net = nn.HybridSequential()
net.add(nn.Dense(16))
net.add(nn.Activation('relu'))
net.add(nn.Dense(4))
net.hybridize()
net.initialize(ctx=gpu_ctx_l)
trainer = Trainer(net.collect_params(), 'adam')
for i in range(100):
with mx.autograd.record():
data = mx.np.random.normal(0, 1, (8, 4), ctx=gpu_ctx_l[0])
out = net(data)
loss = mx.np.square(out - data).sum()
loss.backward()
reporter(loss=loss.asnumpy().item(), iteration=i)
trainer.step(1.0)
def run_tuning_jobs(fn, search_space):
args_decorator = ag.args(**search_space)
scheduler = ag.scheduler.FIFOScheduler(args_decorator(fn),
resource={'num_cpus': 4, 'num_gpus': 1},
num_trials=20,
reward_attr='loss',
time_attr='iteration')
scheduler.run()
scheduler.join_jobs()
return scheduler
search_space = {
'num_hidden': ag.space.Int(16, 32),
'lr': ag.space.Real(1e-3, 1e-2)
}
net = Net()
scheduler = run_tuning_jobs(net.train_fn, search_space)
import numpy as np
import autogluon as ag
import mxnet as mx
from mxnet.gluon import nn, Trainer
from mxnet.util import use_np
@use_np
class Net:
def train_fn(self, args, reporter):
gpu_ctx_l = [mx.gpu(i) for i in range(mx.context.num_gpus())]
print('num_gpus:', len(gpu_ctx_l))
np.random.seed(123)
mx.random.seed(123)
net = nn.HybridSequential()
net.add(nn.Dense(16))
net.add(nn.Activation('relu'))
net.add(nn.Dense(4))
net.hybridize()
net.initialize(ctx=gpu_ctx_l)
trainer = Trainer(net.collect_params(), 'adam')
for i in range(100):
with mx.autograd.record():
data = mx.np.random.normal(0, 1, (8, 4), ctx=gpu_ctx_l[0])
out = net(data)
loss = mx.np.square(out - data).sum()
loss.backward()
reporter(loss=loss.asnumpy().item(), iteration=i)
trainer.step(1.0)
def run_tuning_jobs(fn, search_space):
args_decorator = ag.args(**search_space)
scheduler = ag.scheduler.FIFOScheduler(args_decorator(fn),
resource={'num_cpus': 4, 'num_gpus': 1},
num_trials=20,
reward_attr='loss',
time_attr='iteration')
scheduler.run()
scheduler.join_jobs()
return scheduler
search_space = {
'num_hidden': ag.space.Int(16, 32),
'lr': ag.space.Real(1e-3, 1e-2)
}
net = Net()
# Add one line
a = mx.np.ones((10,), ctx=mx.gpu())
scheduler = run_tuning_jobs(net.train_fn, search_space)
For Ray/Tune, we are able to do this:
@zhreshold @szha @jwmueller @Innixma @Jerryzcn
import numpy as np
import autogluon as ag
import ray
from ray import tune
import mxnet as mx
from mxnet.gluon import nn, Trainer
from mxnet.util import use_np
def get_mxnet_visible_gpus():
"""Get the number of GPUs that are visible to MXNet.
Returns
-------
ctx_l
The ctx list
"""
import mxnet as mx
gpu_count = 0
while True:
try:
arr = mx.np.array(1.0, ctx=mx.gpu(gpu_count))
arr.asnumpy()
gpu_count += 1
except Exception:
break
return [mx.gpu(i) for i in range(gpu_count)]
@use_np
class Net:
def train_fn(self, args, reporter):
np.random.seed(123)
mx.random.seed(123)
gpu_ctx_l = get_mxnet_visible_gpus()
print(gpu_ctx_l)
net = nn.HybridSequential()
net.add(nn.Dense(args['num_hidden']))
net.add(nn.Activation('relu'))
net.add(nn.Dense(4))
net.hybridize()
net.initialize(ctx=gpu_ctx_l)
trainer = Trainer(net.collect_params(), 'adam', {'learning_rate': args['lr']})
for i in range(10):
with mx.autograd.record():
loss_l = []
for ctx in gpu_ctx_l:
data = mx.np.random.normal(0, 1, (8, 4), ctx=ctx)
out = net(data)
loss = mx.np.square(out - data).sum()
loss_l.append(loss)
for loss in loss_l:
loss.backward()
sum_loss = sum([loss.asnumpy() for loss in loss_l])
reporter(loss=-sum_loss, iteration=i)
trainer.step(1.0)
return net
search_space = {
'num_hidden': tune.sample_from(lambda _: np.random.randint(16, 32)),
'lr': tune.sample_from(lambda _: np.random.uniform(1e-3, 1e-2))
}
a = mx.np.ones((10,), ctx=mx.gpu())
net = Net()
analysis = tune.run(net.train_fn, config=search_space, num_samples=16, resources_per_trial={'gpu': 2})
print(analysis.dataframe())
Also @sxjscience pointed out the reason tasks like ImageClassification get around this issue is their task.fit() returns models to the CPU instead of GPU, which seems undesirable...
@sxjscience any clue how ray tune bypassed the issue? It's unclear to me how global imported module is still available while cuda array is not.
@zhreshold It seems that ray tune does not have the issue even if we are reusing the global variable. The following runs well in ray/tune.
import numpy as np
import autogluon as ag
import ray
from ray import tune
import torch as th
import torch.nn as nn
def objective(step, alpha, beta):
return (0.1 + alpha)
class Net(nn.Module):
def __init__(self, in_units, num_hidden):
super().__init__()
self.linear1 = nn.Linear(in_units, num_hidden)
self.out_layer = nn.Linear(num_hidden, 1)
def forward(self, x):
return self.out_layer(nn.ReLU()(self.linear1(x)))
def train_nn(config):
global a_gpu
num_hidden = config['num_hidden']
lr = config['lr']
batch_size, in_units = 32, 8
net = Net(in_units=in_units, num_hidden=num_hidden)
net.cuda()
net.train()
optimizer = th.optim.Adam(net.parameters(), lr=lr, amsgrad=True)
for i in range(10):
optimizer.zero_grad()
random_data_in = th.normal(0, 1, (batch_size, in_units)).cuda()
random_data_out = 5 * random_data_in ** 2 + 1
pred_out = net(random_data_in)
loss = ((random_data_out - pred_out)**2).mean()
loss.backward()
with th.no_grad():
print(a_gpu)
print(loss.cpu().detach().numpy())
tune.report(mean_loss=loss.cpu().detach().numpy())
optimizer.step()
search_space = {
'num_hidden': tune.sample_from(lambda _: np.random.randint(16, 32)),
'lr': tune.sample_from(lambda _: np.random.uniform(1e-3, 1e-2))
}
# Create a tensor in GPU
global a_gpu
a_gpu = th.ones((10,)).cuda()
analysis = tune.run(train_nn, config=search_space, num_samples=4, resources_per_trial={'gpu': 1})
print(analysis.dataframe(metric='mean_loss', mode='min'))
print("Best config: ", analysis.get_best_config(metric="mean_loss", mode="min"))
b_gpu = th.ones((10,)).cuda()
print(b_gpu + a_gpu)
@gradientsky Pinging in case this is of interest to you
Update on the reproducible example with latest AutoGluon after refactory:
import numpy as np
import autogluon as ag
import autogluon.core
import mxnet as mx
from mxnet.gluon import nn, Trainer
from mxnet.util import use_np
@use_np
class Net:
def train_fn(self, args, reporter):
gpu_ctx_l = [mx.gpu(i) for i in range(mx.context.num_gpus())]
print('num_gpus:', len(gpu_ctx_l))
np.random.seed(123)
mx.random.seed(123)
net = nn.HybridSequential()
net.add(nn.Dense(16))
net.add(nn.Activation('relu'))
net.add(nn.Dense(4))
net.hybridize()
net.initialize(ctx=gpu_ctx_l)
trainer = Trainer(net.collect_params(), 'adam')
for i in range(100):
with mx.autograd.record():
data = mx.np.random.normal(0, 1, (8, 4), ctx=gpu_ctx_l[0])
out = net(data)
loss = mx.np.square(out - data).sum()
loss.backward()
reporter(loss=loss.asnumpy().item(), iteration=i)
trainer.step(1.0)
def run_tuning_jobs(fn, search_space):
args_decorator = ag.core.args(**search_space)
scheduler = ag.core.scheduler.FIFOScheduler(args_decorator(fn),
resource={'num_cpus': 4, 'num_gpus': 1},
num_trials=20,
reward_attr='loss',
time_attr='iteration')
scheduler.run()
scheduler.join_jobs()
return scheduler
search_space = {
'num_hidden': ag.core.space.Int(16, 32),
'lr': ag.core.space.Real(1e-3, 1e-2)
}
net = Net()
# Add one line
a = mx.np.ones((10,), ctx=mx.gpu())
scheduler = run_tuning_jobs(net.train_fn, search_space)
My guess is that switching to forkserver can help solve the problem.