GPU do Twoich projektów · płatność krypto bez KYC Jak wynająć
Polski
Otwórz konsolę
Przewodnik praktyczny / KERNODECK

DataLoader się blokuje: sprawdź dane przed workerami

Zacznij od num_workers=0 i stałej kolejności, sprawdź pojedynczy przykład, a potem cały batch, i rozdziel odczyt, transformacje, składanie oraz transfer na GPU. Następnie stopniowo wprowadzaj workery. GPU, które czeka, nie dowodzi, że storage jest wolny: błąd w danych, serializacja lub kosztowne składanie może zablokować łańcuch przed obliczeniami.

6 min czytania · Przewodnik dla programistów

Zdefiniuj, co ma dostarczać loader

Napisz kontrakt wyjściowy, zanim zaczniesz optymalizować: liczba elementów, typ każdego pola, wymiary, zakres celów i reguła dla niekompletnych wejść. Odróżnij identyfikator przykładu od jego pozycji w batchu. Transformacja może zmienić kształt lub odfiltrować wejście; program treningowy musi wiedzieć, czy jest to dozwolone.

Weź reprezentatywny przykład obejmujący zwykły plik, przypadek brzegowy i ostatni element zbioru. Otwórz każdy element z dokładnie takim samym przygotowaniem jak Dataset. Następnie zbadaj ich złożenie. Udany pojedynczy dostęp nie dowodzi, że kilka wyników da się ułożyć w stos. Dla tekstu udokumentuj padding i maskę; dla obrazu kanały, wymiary i kolejność osi.

Ustal zakres: dane lokalne czy zdalne, dekodowanie wliczone czy nie, transformacje stałe czy losowe. Trzymaj go między dwoma pomiarami; pozorny zysk może pochodzić z usuniętej pracy.

Wróć do jednego procesu, aby odczytać błąd

Najpierw odtwórz problem z num_workers=0, shuffle=False i małym batchem. Ładowanie odbywa się wtedy w procesie głównym, a ślad błędu jest zwykle czytelniejszy. Dokumentacja DataLoader zaleca tę możliwość do debugowania. Zapisuj identyfikator elementu, który zawodzi, przed jego dekodowaniem, nie kopiując jego wrażliwej zawartości do logów.

Działaj przez rozdzielenie: surowy dostęp, transformacja, collate_fn, a potem transfer. Jeśli ścieżka zawodzi przed transferem, modyfikowanie CUDA nie jest pierwszym tropem. Jeśli blokuje się tylko przy wielu workerach, zbadaj obiekty i zasoby przekazywane do tych procesów. Porównaj pierwszą iterację z kolejnymi: start workerów może wyjaśniać początkowe oczekiwanie, nie świadcząc o nawracającym problemie.

Timeout może uwidocznić oczekiwanie, ale nie naprawi ani niedostępnego źródła, ani zablokowanego workera. Zapamiętaj ostatni znany krok i zmniejsz liczbę wejść, zamiast wydłużać ten limit w nieskończoność.

Przykład przeanalizowany: trzy oczekiwane kanały, inny obraz

Rozważmy cztery rekordy dydaktyczne. Pierwsze trzy dają tensor o kształcie [3, 16, 16], czwarty [1, 16, 16]. Przy kontrakcie wymagającym trzech kanałów czwarty element musi zostać zidentyfikowany przed złożeniem. Ten scenariusz nie został tu uruchomiony; opisuje oczekiwany wynik na podstawie wybranych kształtów.

Poniższa funkcja zakłada, że każdy rekord ma pola id, x i y, że x jest tensorem CPU, a y jest indeksem całkowitym. Odrzuca niespójność, zamiast po cichu usuwać obraz. W swoim projekcie zdecyduj wyraźnie, czy obraz monochromatyczny ma być konwertowany na trzy kanały, czy odrzucany przy imporcie. Decyzja zależy od znaczenia danych i oczekiwanego przez model przetwarzania wstępnego.

Po poprawce wszystkie cztery identyfikatory powinny pozostać obecne, a złożony tensor powinien mieć kształt [4, 3, 16, 16]. Dodaj zabezpieczenie dopasowane do celów: poprawnie zwymiarowany obraz może wciąż nieść nieprawidłową adnotację.

Proponowane składanie dydaktyczne, nieuruchomione
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"Nieoczekiwany kształt dla {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 to Twój Dataset zwracający opisane rekordy.
# W skrypcie wieloprocesowym utwórz loader pod osłoną main.
if __name__ == "__main__":
    loader = DataLoader(dataset, batch_size=4, num_workers=0,
                        shuffle=False, collate_fn=assemble)
    iterator = iter(loader)
    batch = next(iterator)

Przywróć workery bez zmiany danych

Przejdź od zera do małej liczby workerów, zachowując batch, kolejność i transformacje. Przetestuj pełną epokę, a potem drugą: niektóre błędy pojawiają się dopiero przy ponownym uruchomieniu iteratora albo gdy zasoby zostały już zużyte. Zwiększanie równoległości ma sens tylko wtedy, gdy praca przygotowawcza rzeczywiście może postępować równolegle.

Metody startu zależą od systemu i wersji Pythona. Przy spawn zabezpiecz wejście programu przez if __name__ == '__main__' i definiuj Dataset, collate_fn oraz funkcje workerów na poziomie modułu, a nie w lokalnych lambdach. Dokumentacja procesów wyjaśnia też, dlaczego odziedziczone blokady lub wątki mogą powodować zakleszczenia. Utrzymuj inicjalizację dostępów właściwą dla każdego procesu, gdy biblioteka tego wymaga.

W przypadku IterableDataset sprawdź podział między workerami za pomocą identyfikatorów: kilku pracowników nie powinno każdy z osobna konsumować tego samego strumienia. Nie oceniaj tylko liczby batchy; szukaj też duplikatów i brakujących elementów.

Zmierz oczekiwanie i przepustowość w jasnej jednostce

Użyj dwóch uzupełniających się obserwacji. Sam przebieg przez loader liczy przykłady przygotowane w zdefiniowanym przedziale. Przebieg zintegrowany bada, co się dzieje, gdy model konsumuje te dane. Pierwszy pomaga wyizolować przygotowanie; nie odzwierciedla automatycznie przepustowości trenowania.

W swoim protokole licz przykłady faktycznie dostarczone, a następnie podziel przez upływające sekundy. Zadeklaruj przebiegi wykluczone na rozruch, cache danych, transformacje i liczbę powtórzeń. Zachowaj wartości z każdego przebiegu zamiast wybierać tylko najlepszy. Tabela poniżej to arkusz pomiarowy: żadna wydajność nie jest wypełniona.

Jeśli kształty się zmieniają, liczba przykładów na sekundę może maskować zmianę obciążenia. Dodaj właściwą jednostkę, taką jak zdekodowane piksele lub faktycznie przygotowane tokeny, zachowując również przykłady. Aby zlokalizować oczekiwania w pełnej pętli, nazwij odczyt następnego batcha oddzielnie od obliczeń.

Przewiń tabelę, aby zobaczyć wszystkie kolumny.
Arkusz do wypełnienia własnym obciążeniem, bez zakładanych wartości wydajności
UstawienieSprawdzone elementyZaobserwowany czasOczekiwany wniosek
workers=0Identyfikatory, kształty, celeDo zmierzenia w sekundachPrawidłowe odniesienie
Mała liczba workerówTen sam zestaw wejśćDo zmierzenia w sekundachRzeczywisty zysk lub narzut
To samo ustawienie, druga epokaBrak utraty i duplikacjiDo zmierzenia w sekundachWpływ rozruchu i cache'ów

Traktuj pamięć, wstępne ładowanie i transfery osobno

Workery i batchе w oczekiwaniu zużywają pamięć hosta. Obserwuj ją podczas testu, zanim uznasz, że liczy się tylko VRAM. Głębsze wstępne ładowanie może przesunąć oczekiwanie, zwiększając jednocześnie zajętość; nie gwarantuje więcej wyników na sekundę. Najpierw zredukuj podejrzaną zmienną i porównaj ten sam zakres.

pin_memory i nieblokujące transfery dotyczą przejścia danych do akceleratora. Przepis optymalizacji PyTorch przedstawia je jako dźwignie, które należy zbadać wraz ze sprzętem i obciążeniem. Nie naprawiają błędnego dekodowania. Zacznij od danych CPU w workerach, a następnie zorganizuj transfer w procesie sterującym obliczeniami. Korzyść i faktyczne nakładanie się muszą być zaobserwowane, a nie założone.

Jeśli używasz persistent_workers, pamiętaj o zasobach i stanie zachowywanym między epokami. Zadowalające ustawienie na jednym batchu nie wystarczy, aby zweryfikować zamykanie plików czy odświeżanie źródła.

Akceptuj ustawienie tylko wtedy, gdy dane pozostają poprawne

Oczekiwany rezultat to pętla, która otrzymuje wszystkie przewidziane dane wejściowe, w wybranych ramach, bez cichego błędu. Porównaj identyfikatory i cele przed optymalizacją i po niej. Wyjaśnij drop_last, jeśli pomijasz ostatni niepełny batch. Jeśli transformacje są losowe, kontroluj ich politykę, zamiast wymagać równości pikseli, która byłaby z tą polityką sprzeczna.

Zachowaj najprostsze ustawienie, które odpowiada zmierzonej potrzebie. Zwiększenie liczby workerów może nic nie poprawić, jeśli pamięć masowa, dekodowanie lub model już narzucają limit. Zasobów CPU, RAM i pamięci masowej serwera nie można wywnioskować z nazwy jego GPU: określ te potrzeby osobno, gdy przygotowujesz swoje środowisko Kernodeck.

Twoje pytania

Czy num_workers=0 wyłącza trening na GPU?

Nie. Umieszcza ładowanie danych w procesie głównym. Model nadal może obliczać na GPU. To ustawienie przede wszystkim pozwala bardziej bezpośrednio odczytać błędy czytania, transformacji i składania.

Czy należy wybrać tyle workerów, ile jest rdzeni CPU?

Nie automatycznie. Właściwe ustawienie zależy od pracy przygotowawczej, pamięci, dostępu do danych i szybkości konsumpcji przez model. Porównaj kilka wartości, zachowując to samo obciążenie i kontrolując dostarczane dane wejściowe.

Czy mniejszy batch naprawia zatrzymujący się worker?

Może zmienić presję na pamięć, ale nie naprawia nieprawidłowej adnotacji, zasobu, którego nie można zserializować, ani nieczytelnego pliku. Najpierw odtwórz problem z zerową liczbą workerów, aby zidentyfikować winny etap.

Czy czas spędzony w next(iterator) mierzy dysk?

Nie. Po iterator=iter(loader), next(iterator) czeka na batch. Mogą w tym uczestniczyć dekodowanie, transformacje, składanie, komunikacja między procesami i wstępne ładowanie. Pomiar samego czytania i ślad pełnej pętli odpowiadają na różne pytania.