Проблема
В настоящее время я работаю над проектом, которым, к сожалению, не могу поделиться с вами. Проект о гиперпараметрической оптимизации для нейронных сетей, и требует, чтобы я параллельно обучал несколько моделей нейронных сетей (больше, чем я могу хранить на моем GPU) Сетевые архитектуры остаются прежними, но сетевые параметры и гиперпараметры подвергаются изменениям между каждым интервалом обучения. В настоящее время я достигаю этого, используя PyTorch в среде linux, чтобы позволить NVIDIA GTX 1660 (6 ГБ ОЗУ) использовать функцию многопроцессорной обработки, которую предоставляет PyTorch.
Код (упрощенный):
def training_function(checkpoint):
load(checkpoint)
train(checkpoint)
unload(checkpoint)
for step in range(training_steps):
trained_checkpoints = list()
for trained_checkpoint in pool.imap_unordered(training_function, checkpoints):
trained_checkpoints.append(trained_checkpoint)
for optimized_checkpoint in optimize(trained_checkpoints):
checkpoints.update(optimized_checkpoint)
В настоящее время я тестирую с набором из 30 нейронных сетей (то есть с 30 контрольными точками) с наборами данных MNIST и FashionMNIST, которые состоят из 70 000 (50k обучение, 10k проверка, 10k тестирование) изображений 28x28 с 1 каналом, соответственно. Сеть, которую я обучаю, представляет собой простую реализацию Lenet5.
Я использую пул torch.multiprocessing и позволяю создавать 7 процессов. Каждый процесс использует часть доступной памяти графического процессора только для инициализации среды CUDA в каждом процессе. После обучения контрольные точки адаптируются с помощью моей техники оптимизации гиперпараметров.
Функция load
в training_function
загружает состояние модели и оптимизатора (содержит тензоры сетевых параметров) из локального файла в Память GPU с использованием torch.load
. unload
сохраняет вновь обученные состояния обратно в файл, используя torch.save
, и удаляет их из памяти. Я делаю это потому, что PyTorch будет отсоединять тензоры GPU только тогда, когда на них не ссылается ни одна переменная. Я должен сделать это, потому что у меня ограниченная память GPU.
Текущая настройка работает, но каждая инициализация CUDA занимает более 700 МБ ОЗУ GPU, и поэтому мне интересно, есть ли другие способы, которые я мог бы сделать это, которые могут использовать меньше памяти без ущерба для эффективности.
Мои попытки
Я подозревал, что могу использовать пул потоков для экономии памяти, и это имело место. Создав 7 потоков вместо 7 процессов, CUDA был инициализирован только один раз, что сэкономило почти половину моей памяти. Тем не менее, это приводит к новой проблеме, в которой графический процессор используется только ок. 30% загрузка в соответствии с nvidia-smi, которую я отслеживаю в отдельном linux терминале. Без потоков я получаю около 85-90% использования.
Я также перепутал с torch.multiprocessing.set_sharing_strategy
, который в настоящее время установлен как 'file_descriptor', но безуспешно.
Мои вопросы
- Существует ли лучший способ работы с несколькими состояниями модели и оптимизатора без сохранения и загрузки их в файлы во время обучения? Я пытался переместить модель в ЦП с помощью
model.cpu()
перед сохранением state_dict, но в моей реализации это не сработало (утечки памяти). - Существует ли эффективный способ обучения нескольких нейронных сетей на в то же время, что использует меньше памяти GPU? При поиске в Интернете я нахожу ссылки только на
nn.DataParallel
, который обучает одну и ту же модель на нескольких графических процессорах, копируя ее на каждый графический процессор. Это не работает для моей проблемы.
У меня скоро будет доступ к нескольким, более мощным графическим процессорам с большим объемом памяти, и я подозреваю, что эта проблема будет менее раздражающей, но я не удивлюсь если есть лучшее решение, я не получаю.
Обновление (09.03.2020)
Для любых будущих читателей, если вы собираетесь сделать что-то похожее на псевдокод, показанный выше, и вы планируете использовать несколько графических процессоров, поэтому обязательно создайте один пул многопроцессорной обработки для каждого устройства графического процессора. Пулы не выполняют функции в соответствии с базовыми процессами, которые он содержит, и поэтому вы в конечном итоге инициализируете CUDA несколько раз в одном и том же процессе, тратя впустую память.
Еще одно важное замечание: при передаче устройство (например, 'cuda: 1') для каждой torch.cuda
-функции, вы можете обнаружить, что torch делает что-то с устройством cuda по умолчанию 'cuda: 0' где-то в коде, инициализируя CUDA на этом устройстве для каждого процесса, который тратит память на нежелательная и ненужная инициализация CUDA. Я исправил эту проблему с помощью with torch.cuda.device(device_id)
, который инкапсулирует все training_function
.
. В итоге я не использовал многопроцессорные пулы и вместо этого определил свой собственный класс процессов, который содержит устройство и функцию обучения. Это означает, что мне нужно поддерживать очереди для каждого процесса устройства, но все они имеют одну и ту же очередь, то есть я могу получить результаты, как только они станут доступны. Я подумал, что написать собственный класс процесса проще, чем написать собственный класс пула. Я отчаянно пытался продолжать использовать пулы, поскольку они легко поддерживаются, но мне пришлось использовать несколько imap
-функций, и поэтому результаты не могли быть получены по одному за раз, что приводило к менее эффективной тренировке - l oop.
Сейчас я успешно тренируюсь на нескольких графических процессорах, но мои вопросы, опубликованные выше, все еще остаются без ответа.
Обновление (10.03.2020 )
Я реализовал другой способ хранения заявлений модели и оптимизатора вне ОЗУ графического процессора. Я написал функцию, которая заменяет каждый тензор в диктантах на .to('cpu')
эквивалент. Это стоит мне немного памяти процессора, но это более надежно, чем хранение локальных файлов.