GPU cho dự án của bạn · thanh toán crypto không cần KYC Cách thuê
Tiếng Việt
Mở console
Hướng dẫn thực hành / KERNODECK

DataLoader bị kẹt: kiểm tra dữ liệu trước các worker

Hãy bắt đầu với num_workers=0 và một thứ tự cố định, kiểm tra một mẫu rồi đến một batch đầy đủ, và tách riêng việc đọc, biến đổi, gom batch và truyền lên GPU. Sau đó đưa dần các worker trở lại. Một GPU đang chờ không chứng minh rằng lưu trữ chậm: một lỗi dữ liệu, một bước tuần tự hóa hay gom batch tốn kém có thể làm kẹt cả chuỗi trước khi tính toán.

10 phút đọc · Hướng dẫn cho nhà phát triển

Xác định rõ bộ nạp phải trả ra cái gì

Hãy viết bản giao kèo đầu ra trước khi tối ưu: số phần tử, kiểu của từng trường, kích thước, miền giá trị của nhãn và quy tắc cho các đầu vào không đầy đủ. Phân biệt mã định danh của mẫu với vị trí của nó trong một batch. Một phép biến đổi có thể thay đổi hình dạng hoặc lọc bỏ một đầu vào; chương trình huấn luyện phải biết liệu điều đó có được phép hay không.

Hãy lấy một mẫu đại diện bao gồm một tệp thông thường, một trường hợp biên và phần tử cuối cùng của tập dữ liệu. Mở từng phần tử với đúng cách chuẩn bị như trong Dataset. Sau đó xem xét việc gom batch chúng. Việc truy cập riêng lẻ thành công không chứng minh rằng nhiều kết quả có thể xếp chồng lên nhau. Với văn bản, hãy ghi rõ padding và mask; với ảnh, hãy ghi rõ kênh, kích thước và thứ tự các trục.

Hãy chốt phạm vi: dữ liệu có sẵn cục bộ hay ở xa, có bao gồm giải mã hay không, biến đổi cố định hay ngẫu nhiên. Hãy giữ nguyên nó giữa hai lần đo; một mức tăng biểu kiến có thể đến từ việc đã bỏ đi một công đoạn.

Quay lại chế độ một tiến trình để đọc lỗi

Trước tiên hãy tái hiện với num_workers=0, shuffle=False và một batch nhỏ. Khi đó việc tải diễn ra trong tiến trình chính và dấu vết lỗi thường dễ đọc hơn. Tài liệu DataLoader khuyến nghị khả năng này để gỡ lỗi. Hãy ghi lại mã định danh của phần tử bị lỗi trước khi giải mã nó, mà không sao chép nội dung nhạy cảm của nó vào nhật ký.

Hãy làm theo cách tách riêng: truy cập thô, biến đổi, collate_fn, rồi truyền. Nếu quá trình thất bại trước khi truyền, thì việc sửa CUDA không phải là hướng đầu tiên. Nếu nó chỉ bị kẹt khi có nhiều worker, hãy xem xét các đối tượng và tài nguyên được truyền cho các tiến trình đó. Hãy so sánh lần lặp đầu tiên với các lần sau: việc khởi động các worker có thể giải thích một khoảng chờ ban đầu mà không chứng minh có vấn đề lặp lại.

Một timeout có thể khiến khoảng chờ trở nên hữu hình, nhưng không sửa được nguồn dữ liệu không khả dụng lẫn một worker bị kẹt. Hãy giữ bước cuối cùng đã biết và giảm số lượng đầu vào thay vì tăng vô hạn thời gian chờ đó.

Ví dụ minh họa: mong đợi ba kênh, nhưng lại có một ảnh khác

Hãy xét bốn bản ghi minh họa. Ba bản ghi đầu cho một tensor có hình dạng [3, 16, 16], bản ghi thứ tư có [1, 16, 16]. Với một giao kèo buộc phải có ba kênh, phần tử thứ tư phải được nhận diện trước khi xếp chồng. Kịch bản này chưa được chạy ở đây; nó mô tả một kết quả mong đợi từ các hình dạng đã chọn.

Hàm dưới đây giả định rằng mỗi bản ghi có các trường id, x và y, rằng x là một tensor CPU và y là một chỉ số nguyên. Hàm từ chối sự không nhất quán thay vì âm thầm xóa bỏ ảnh. Với dự án của bạn, hãy quyết định rõ ràng liệu một ảnh đơn sắc phải được chuyển thành ba kênh hay bị loại bỏ ngay khi nhập. Quyết định này phụ thuộc vào ý nghĩa của dữ liệu và bước tiền xử lý mà mô hình mong đợi.

Sau khi sửa, cả bốn mã định danh phải vẫn còn hiện diện và tensor được gom lại phải có hình dạng [4, 3, 16, 16]. Hãy thêm một ràng buộc phù hợp cho các nhãn: một ảnh có kích thước đúng vẫn có thể mang một chú thích không hợp lệ.

Đoạn mã gom batch minh họa được đề xuất, chưa chạy thử
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"Hình dạng không mong đợi cho {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 là Dataset của bạn tạo ra các bản ghi được mô tả.
# Trong một script đa tiến trình, hãy tạo loader bên trong khối bảo vệ main.
if __name__ == "__main__":
    loader = DataLoader(dataset, batch_size=4, num_workers=0,
                        shuffle=False, collate_fn=assemble)
    iterator = iter(loader)
    batch = next(iterator)

Đưa workers trở lại mà không thay đổi dữ liệu

Chuyển từ không có worker nào lên một số lượng nhỏ worker trong khi vẫn giữ nguyên batch, thứ tự và các phép biến đổi. Hãy thử nghiệm một epoch đầy đủ, rồi thêm một epoch nữa: một số lỗi chỉ xuất hiện khi khởi động lại một iterator hoặc khi tài nguyên đã bị tiêu thụ. Việc tăng mức song song chỉ hữu ích nếu công việc chuẩn bị thực sự có thể tiến triển song song.

Các phương thức khởi động phụ thuộc vào hệ thống và phiên bản Python. Với spawn, hãy bảo vệ phần vào của chương trình bằng if __name__ == '__main__' và định nghĩa Dataset, collate_fn cùng các hàm worker ở cấp module thay vì trong các lambda cục bộ. Tài liệu về tiến trình cũng giải thích vì sao các lock hoặc thread được kế thừa có thể gây ra tắc nghẽn. Hãy giữ việc khởi tạo các truy cập riêng cho từng tiến trình khi thư viện yêu cầu.

Với một IterableDataset, hãy kiểm tra việc phân vùng giữa các worker bằng các mã định danh: nhiều worker không được mỗi cái tiêu thụ toàn bộ cùng một luồng. Đừng chỉ đánh giá số lượng batch; hãy tìm cả các phần tử trùng lặp và thiếu sót.

Đo thời gian chờ và thông lượng bằng một đơn vị rõ ràng

Hãy dùng hai quan sát bổ trợ cho nhau. Một lượt chạy chỉ riêng loader đếm các mẫu được chuẩn bị trong một khoảng thời gian xác định. Một lượt chạy tích hợp xem xét điều gì xảy ra khi mô hình tiêu thụ dữ liệu đó. Cách thứ nhất giúp tách riêng phần chuẩn bị; nó không tự động đại diện cho thông lượng huấn luyện.

Trong quy trình của bạn, hãy đếm số mẫu thực sự được cung cấp, rồi chia cho số giây đã trôi qua. Hãy công bố rõ các lượt bị loại trừ cho khởi động, cache dữ liệu, các phép biến đổi và số lần lặp lại. Hãy giữ giá trị của từng lượt thay vì chỉ chọn lượt tốt nhất. Bảng dưới đây là một bảng ghi chép: không có chỉ số hiệu năng nào được điền sẵn.

Nếu các hình dạng thay đổi, một con số mẫu mỗi giây có thể che khuất sự thay đổi tải. Hãy thêm đơn vị phù hợp, chẳng hạn như pixel được giải mã hoặc token thực sự được chuẩn bị, đồng thời vẫn giữ các mẫu. Để định vị các khoảng chờ trong toàn bộ vòng lặp, hãy đặt tên riêng cho việc đọc batch tiếp theo và cho phần tính toán.

Cuộn bảng để xem tất cả các cột.
Bảng ghi chép để điền với tải của bạn, không có giá trị hiệu năng giả định
Thiết lậpCác yếu tố được kiểm traThời lượng quan sát đượcKết luận mong đợi
workers=0Mã định danh, hình dạng, nhãn mục tiêuCần đo bằng giâyĐường cơ sở chính xác
Số lượng nhỏ workersCùng một tập đầu vàoCần đo bằng giâyLợi ích hoặc chi phí phát sinh thực tế
Cùng thiết lập, epoch thứ haiKhông mất mát cũng không trùng lặpCần đo bằng giâyẢnh hưởng của khởi động và các cache

Xử lý bộ nhớ, tải trước và truyền tải một cách riêng biệt

Các worker và các batch đang chờ tiêu tốn bộ nhớ máy chủ. Hãy theo dõi nó trong quá trình thử nghiệm trước khi kết luận rằng chỉ VRAM mới quan trọng. Tải trước sâu hơn có thể dịch chuyển khoảng chờ trong khi làm tăng mức sử dụng; nó không đảm bảo nhiều kết quả hơn mỗi giây. Trước tiên hãy giảm biến số bị nghi ngờ và so sánh trên cùng một phạm vi.

pin_memory và các truyền tải không chặn liên quan đến việc chuyển dữ liệu sang một bộ tăng tốc. Công thức tối ưu hóa PyTorch giới thiệu chúng như những đòn bẩy cần xem xét cùng với phần cứng và tải. Chúng không sửa chữa một giải mã sai. Hãy bắt đầu bằng dữ liệu trên CPU trong các worker, rồi tổ chức việc truyền tải trong tiến trình điều khiển tính toán. Lợi ích và mức chồng lấn thực tế phải được quan sát, không phải giả định.

Nếu dùng persistent_workers, hãy lưu ý đến tài nguyên và trạng thái được giữ lại giữa hai epoch. Một cấu hình chạy tốt trên một batch duy nhất chưa đủ để xác minh việc đóng tệp hay làm mới nguồn dữ liệu.

Chỉ chấp nhận một cấu hình khi dữ liệu vẫn đúng

Kết quả mong đợi là một vòng lặp nhận đủ mọi đầu vào dự kiến, trong khung đã chọn, không có lỗi âm thầm. Hãy so sánh các định danh và nhãn trước và sau khi tối ưu. Giải thích drop_last nếu bạn loại bỏ batch cuối không đầy đủ. Nếu các phép biến đổi là ngẫu nhiên, hãy kiểm soát chính sách của chúng thay vì đòi hỏi sự bằng nhau từng pixel vốn đi ngược lại chính sách đó.

Hãy giữ cấu hình đơn giản nhất đáp ứng nhu cầu đã đo lường. Việc tăng số lượng worker có thể không cải thiện gì nếu lưu trữ, giải mã hoặc mô hình đã là giới hạn. Tài nguyên CPU, RAM và lưu trữ của một server không thể suy ra từ tên GPU của nó: hãy nêu rõ các nhu cầu này một cách riêng biệt khi bạn chuẩn bị môi trường Kernodeck của mình.

Câu hỏi của bạn

num_workers=0 có vô hiệu hóa việc huấn luyện trên GPU không?

Không. Nó đặt việc tải dữ liệu vào tiến trình chính. Mô hình vẫn có thể tính toán trên GPU. Cấu hình này chủ yếu giúp đọc trực tiếp hơn các lỗi đọc, biến đổi và lắp ghép.

Có nên chọn số worker bằng số lõi CPU không?

Không tự động. Cấu hình đúng phụ thuộc vào công việc chuẩn bị, bộ nhớ, truy cập dữ liệu và tốc độ tiêu thụ của mô hình. Hãy so sánh vài giá trị trong khi giữ nguyên tải và kiểm soát các đầu vào được cung cấp.

Batch nhỏ hơn có khắc phục được worker bị dừng không?

Nó có thể thay đổi áp lực bộ nhớ, nhưng không khắc phục được nhãn không hợp lệ, tài nguyên không thể tuần tự hóa hay tệp không đọc được. Trước tiên hãy tái tạo với zero worker để xác định bước gây lỗi.

Thời gian trong next(iterator) có đo được đĩa không?

Không. Sau iterator=iter(loader), next(iterator) chờ một batch. Việc giải mã, biến đổi, lắp ghép, giao tiếp giữa các tiến trình và tải trước đều có thể xen vào. Một phép đo chỉ đọc và một dấu vết của vòng lặp đầy đủ trả lời những câu hỏi khác nhau.