Определите, что должен выдавать загрузчик
Опишите контракт вывода до оптимизации: количество элементов, тип каждого поля, размерности, диапазон целевых значений и правило для неполных входных данных. Различайте идентификатор примера и его позицию в батче. Преобразование может изменить форму или отфильтровать входные данные; программа обучения должна знать, допустимо ли это.
Возьмите репрезентативный образец, включающий обычный файл, граничный случай и последний элемент набора. Откройте каждый элемент с точно такой же подготовкой, как в 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.