GPU для ваших проектов · оплата криптовалютой без KYC Как арендовать
Русский
Открыть консоль
Практическое руководство / KERNODECK

DataLoader блокируется: проверьте данные до воркеров

Начните с num_workers=0 и фиксированного порядка, проверьте один образец, затем полный батч и разделите чтение, преобразования, сборку и передачу на GPU. После этого постепенно возвращайте воркеры. Простаивающий GPU не доказывает, что хранилище медленное: ошибка в данных, сериализация или дорогостоящая сборка может заблокировать цепочку до вычислений.

6 мин чтения · Руководство для разработчиков

Определите, что должен выдавать загрузчик

Опишите контракт вывода до оптимизации: количество элементов, тип каждого поля, размерности, диапазон целевых значений и правило для неполных входных данных. Различайте идентификатор примера и его позицию в батче. Преобразование может изменить форму или отфильтровать входные данные; программа обучения должна знать, допустимо ли это.

Возьмите репрезентативный образец, включающий обычный файл, граничный случай и последний элемент набора. Откройте каждый элемент с точно такой же подготовкой, как в Dataset. Затем изучите их сборку. Успешный индивидуальный доступ не доказывает, что несколько результатов можно объединить в стек. Для текста задокументируйте padding и маску; для изображения — каналы, размерности и порядок осей.

Зафиксируйте границы: данные локальные или удалённые, включено ли декодирование, преобразования фиксированные или случайные. Соблюдайте их при обоих настройках; кажущийся выигрыш может быть результатом удалённой работы.

Вернитесь к одному процессу, чтобы прочитать ошибку

Сначала воспроизведите с num_workers=0, shuffle=False и небольшим батчем. Тогда загрузка идёт в главном процессе и трассировка ошибки обычно более читабельна. Документация DataLoader рекомендует эту возможность для отладки. Запишите идентификатор элемента, на котором происходит сбой, до его декодирования, не копируя его конфиденциальное содержимое в журналы.

Действуйте путём разделения: необработанный доступ, преобразование, collate_fn, затем передача. Если путь даёт сбой до передачи, изменение CUDA — не первое, что стоит проверять. Если он блокируется только с несколькими воркерами, изучите объекты и ресурсы, передаваемые этим процессам. Сравните первую и последующие итерации: запуск воркеров может объяснить начальное ожидание, не подтверждая постоянную проблему.

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

Разобранный пример: ожидаются три канала, а изображение другое

Рассмотрим четыре учебные записи. Первые три дают тензор формы [3, 16, 16], четвёртая — [1, 16, 16]. При контракте, требующем три канала, четвёртый элемент должен быть выявлен до объединения в стек. Этот сценарий здесь не выполнялся; он описывает ожидаемый результат исходя из выбранных форм.

Функция ниже предполагает, что каждая запись имеет поля id, x и y, x — это тензор CPU, а y — целочисленный индекс. Она отклоняет несоответствие, а не удаляет изображение незаметно. Для вашего проекта явно решите, следует ли монохромное изображение преобразовать в три канала или отклонить при импорте. Это решение зависит от смысла данных и предобработки, ожидаемой моделью.

После исправления все четыре идентификатора должны остаться на месте, а собранный тензор должен иметь форму [4, 3, 16, 16]. Добавьте проверку, подходящую для целевых значений: изображение с правильными размерностями всё ещё может нести недопустимую аннотацию.

Предлагаемая учебная сборка, не выполнялась
import torch
from torch.utils.data import DataLoader

def assemble(records):
    for item in records:
        if tuple(item["x"].shape) != (3, 16, 16):
            raise ValueError(f"Неожиданная форма для {item['id']}")
    return {
        "ids": [item["id"] for item in records],
        "x": torch.stack([item["x"] for item in records]),
        "y": torch.tensor([item["y"] for item in records],
                          dtype=torch.long),
    }

# dataset — ваш Dataset, возвращающий описанные записи.
# В многопроцессном скрипте создавайте loader под защитой main.
if __name__ == "__main__":
    loader = DataLoader(dataset, batch_size=4, num_workers=0,
                        shuffle=False, collate_fn=assemble)
    iterator = iter(loader)
    batch = next(iterator)

Возвращаем workers, не меняя данные

Перейдите от нуля к небольшому числу workers, сохраняя batch, порядок и преобразования. Проверьте одну полную эпоху, затем вторую: некоторые ошибки проявляются только при перезапуске итератора или когда ресурсы уже израсходованы. Увеличение параллелизма полезно лишь тогда, когда работа по подготовке действительно может выполняться параллельно.

Методы запуска зависят от системы и версии Python. При spawn защитите точку входа программы с помощью if __name__ == '__main__' и определяйте Dataset, collate_fn и функции воркеров на уровне модуля, а не в локальных лямбдах. Документация по процессам также объясняет, почему унаследованные блокировки или потоки могут приводить к зависаниям. Инициализируйте доступы отдельно в каждом процессе, когда этого требует библиотека.

Для IterableDataset проверяйте разбиение между воркерами с помощью идентификаторов: несколько воркеров не должны каждый потреблять один и тот же поток целиком. Оценивайте не только число батчей; ищите также дубликаты и пропущенные элементы.

Измеряем ожидание и пропускную способность в понятных единицах

Используйте два взаимодополняющих наблюдения. Проход только по загрузчику считает примеры, подготовленные за заданный интервал. Интегрированный проход показывает, что происходит, когда модель потребляет эти данные. Первый помогает изолировать подготовку; он не отражает автоматически пропускную способность обучения.

В своём протоколе считайте фактически доставленные примеры, а затем делите на прошедшие секунды. Указывайте исключённые проходы для разогрева, кэш данных, преобразования и число повторов. Сохраняйте значения каждого прохода, а не выбирайте только лучший. Таблица ниже — это бланк для записей: никакие значения производительности в неё не подставлены.

Если формы различаются, число примеров в секунду может скрыть изменение нагрузки. Добавьте подходящую единицу, например декодированные пиксели или фактически подготовленные токены, сохраняя также примеры. Чтобы локализовать ожидания в полном цикле, называйте чтение очередного батча отдельно от вычисления.

Прокрутите таблицу, чтобы увидеть все столбцы.
Бланк для заполнения вашей нагрузкой, без предположений о значениях производительности
НастройкаПроверяемые элементыНаблюдаемая длительностьОжидаемый вывод
workers=0Идентификаторы, формы, целиИзмерить в секундахКорректный эталон
Небольшое число workersТот же набор входных данныхИзмерить в секундахРеальный выигрыш или издержки
Та же настройка, вторая эпохаНи одной потери или дублированияИзмерить в секундахВлияние запуска и кэшей

Разбираем память, предзагрузку и передачи по отдельности

Воркеры и батчи в ожидании потребляют оперативную память. Следите за ней во время теста, прежде чем делать вывод, что важна только VRAM. Более глубокая предзагрузка может сместить ожидание, увеличивая при этом занятую память; она не гарантирует больше результатов в секунду. Сначала уменьшите подозрительную переменную и сравните в одних и тех же границах.

pin_memory и неблокирующие передачи касаются перемещения данных на ускоритель. В рецепте оптимизации PyTorch они представлены как рычаги, которые следует рассматривать с учётом оборудования и нагрузки. Они не исправляют ошибочное декодирование. Начните с данных CPU в воркерах, затем организуйте передачу в процессе, управляющем вычислениями. Выгоду и фактическое перекрытие нужно наблюдать, а не предполагать.

Если используется persistent_workers, учитывайте ресурсы и состояния, сохраняемые между эпохами. Настройки, дающей приемлемый результат на одном батче, недостаточно, чтобы убедиться в закрытии файлов или обновлении источника.

Принимайте настройку только если данные остаются корректными

Ожидаемый результат — цикл, который получает все предусмотренные входы в выбранных рамках, без тихих ошибок. Сравните идентификаторы и цели до и после оптимизации. Поясните drop_last, если вы отбрасываете последний неполный батч. Если преобразования случайны, контролируйте их политику, а не требуйте равенства пикселей, которое противоречило бы этой политике.

Сохраняйте самую простую настройку, отвечающую измеренной потребности. Увеличение числа workers может ничего не улучшить, если хранилище, декодирование или модель уже задают предел. Ресурсы CPU, RAM и хранилища сервера не выводятся из названия его GPU: указывайте эти требования отдельно, когда готовите своё окружение Kernodeck.

Ваши вопросы

Отключает ли num_workers=0 обучение на GPU?

Нет. Он помещает загрузку данных в основной процесс. Модель по-прежнему может вычислять на GPU. Эта настройка прежде всего позволяет напрямую видеть ошибки чтения, преобразования и сборки.

Нужно ли выбирать столько workers, сколько ядер CPU?

Не автоматически. Правильная настройка зависит от работы по подготовке, памяти, доступа к данным и скорости потребления моделью. Сравните несколько значений при одинаковой нагрузке и с контролем подаваемых входов.

Исправляет ли меньший батч останавливающийся worker?

Он может изменить нагрузку на память, но не исправляет недопустимую аннотацию, несериализуемый ресурс или нечитаемый файл. Сначала воспроизведите с нулём workers, чтобы выявить проблемный этап.

Измеряет ли время в next(iterator) нагрузку на диск?

Нет. После iterator=iter(loader) вызов next(iterator) ожидает батч. Декодирование, преобразования, сборка, межпроцессное взаимодействие и предзагрузка могут влиять на это время. Отдельное измерение чтения и трассировка полного цикла отвечают на разные вопросы.