MarkTechPost
85

Tin ngành

Hướng dẫn sử dụng NVIDIA cuDNN Graph API: Tối ưu hóa hiệu năng qua Fusion, Auto-tuning và Plan Reuse

(giờ Việt Nam)

Tóm tắt AI

Bài viết hướng dẫn chi tiết cách sử dụng cuDNN Frontend API để xây dựng đồ thị tính toán, cho phép cuDNN tự động tối ưu hóa và quản lý các kế hoạch thực thi nhằm đạt hiệu suất cao nhất.

Bản dịch AI

Inside NVIDIA’s cuDNN Graph API: Fusion, Autotuning, and Plan Reuse with cuDNN Frontend

Trong hướng dẫn này, chúng ta sẽ làm việc với graph API của cuDNN Frontend từ cấp độ thấp hơn cả framework: chúng ta mô tả một phép tính dưới dạng một đồ thị các toán tử, để cuDNN chọn một engine để chạy nó, và sau đó chúng ta tự mình kiểm soát lựa chọn đó. Mọi kernel chúng ta xây dựng ở đây đều được biểu diễn theo cùng một cách: chúng ta khai báo các tensor theo kích thước và bước nhảy (strides), xâu chuỗi các toán tử vào chúng, chạy quy trình xây dựng năm bước gồm xác thực, xây dựng đồ thị toán tử, tạo kế hoạch thực thi, kiểm tra khả năng hỗ trợ và xây dựng kế hoạch, sau đó thực thi dựa trên một tập hợp các con trỏ (variant pack). Chúng ta chạy tất cả trên một GPU Colab duy nhất, kiểm tra từng kết quả so với tham chiếu từ PyTorch để thấy được cả tính chính xác của quá trình hợp nhất (fusion) lẫn chi phí của nó. Các chủ đề được xây dựng nối tiếp nhau, đi từ một phép tích chập (convolution) hợp nhất đơn lẻ đến việc tự động tinh chỉnh (autotuning) trên các cấu hình engine, các phần kết thúc (epilogues) kiểu FP8, cơ chế attention, tuần tự hóa kế hoạch (plan serialization), các hình dạng động (dynamic shapes) và ghi lại CUDA graph.

Chúng ta bắt đầu bằng việc cài đặt nvidia-cudnn-frontend và giải quyết vấn đề thường gặp trong hầu hết các lần chạy đầu tiên: làm cho libcudnn.so hiển thị với trình tải động của frontend. Chúng ta buộc PyTorch phải tải cuDNN đi kèm của nó trước, sau đó tải trước các đối tượng chia sẻ (shared objects) một cách tường minh, để dlopen của chính frontend có thể phân giải dựa trên một thư viện đã tồn tại trong tiến trình. Sau đó, chúng ta báo cáo khả năng tính toán (compute capability), chọn bfloat16 hoặc float16 cho phù hợp, tạo cuDNN handle và định nghĩa các hàm hỗ trợ cho việc mô tả tensor, xây dựng đồ thị, cấp phát không gian làm việc (workspace) và đo điểm chuẩn dựa trên sự kiện (event-based benchmarking) mà phần còn lại của notebook sẽ tái sử dụng.

Chúng ta xây dựng đồ thị đầu tiên của mình, một phép tích chập theo sau là phép cộng bias và ReLU, tất cả được hợp nhất thành một kernel duy nhất. Chúng ta giữ mọi tensor ở định dạng channels_last vì đó là cách cung cấp cho cuDNN các bước nhảy NHWC mà các engine tensor-core của nó yêu cầu, và chúng ta ghim các kích thước đầu ra và bước nhảy một cách tường minh để kết quả được ghi lại theo đúng bố cục đó. Chúng ta xác thực đầu ra so với torch.nn.functional.conv2d, sau đó đo điểm chuẩn đồ thị hợp nhất so với việc PyTorch chạy phép tích chập và kích hoạt (activation) dưới dạng các kernel riêng biệt.

Chúng ta xây dựng lại cùng một phép tích chập đó nhưng không còn tin tưởng vào heuristic nữa, thay vào đó yêu cầu các kế hoạch từ các chế độ heuristic A, B và FALLBACK, đồng thời biên dịch tất cả chúng với build_plan_policy.ALL. Sau đó, chúng ta duyệt qua danh sách kế hoạch, xây dựng từng cấu hình, cấp phát không gian làm việc cụ thể cho nó và đo thời gian với execute_plan_at_index, in ra thông lượng và kích thước không gian làm việc cho từng ứng viên. Sự chênh lệch giữa engine nhanh nhất và chậm nhất chính là mục đích của bài tập này, vì nó cho chúng ta biết mình đạt được bao nhiêu lợi ích khi sử dụng một chỉ mục đã được tự động tinh chỉnh thay vì chấp nhận lựa chọn mặc định.

Chúng ta chuyển sang phép nhân ma trận (matmul) theo lô và gắn thêm một phần kết thúc (epilogue) đầy đủ vào đó: một hệ số alpha được cung cấp dưới dạng giá trị vô hướng (host scalar), một phép cộng bias, một phép kích hoạt và một phép giảm AMAX trên kết quả. AMAX trong cùng một kernel là mô hình mà quá trình huấn luyện FP8 dựa vào, vì nó thu thập hệ số tỷ lệ cho bước lượng tử hóa tiếp theo mà không cần duyệt qua đầu ra lần thứ hai. Chúng ta so sánh với một chuỗi baddbmm, activation và amax của PyTorch, điều này làm rõ rằng việc tăng tốc đến từ việc loại bỏ lưu lượng bộ nhớ của phần kết thúc thay vì từ một GEMM nhanh hơn.

Chúng ta xây dựng một đồ thị attention tích vô hướng có tỷ lệ (scaled dot-product attention) hợp nhất với mặt nạ nhân quả (causal masking) và kiểm tra nó với torch.nn.functional.scaled_dot_product_attention, bảo vệ toàn bộ phần này bằng kiểm tra SM80 vì các kernel hợp nhất cần kiến trúc Ampere trở lên. Chúng ta viết đối số nhân quả với các phương án dự phòng (fallbacks), vì frontend đã chuyển từ use_causal_mask sang diagonal_alignment và các đối số ràng buộc trong các bản phát hành 1.x của nó. Sau đó, chúng ta tuần tự hóa một đồ thị matmul đã xây dựng thành các byte, tải lại nó vào một đối tượng đồ thị mới và thực thi nó thông qua các UID số nguyên, cho phép chúng ta bỏ qua hoàn toàn chi phí biên dịch khi khởi động tiến trình.

Chúng ta kết thúc với hai mối quan tâm trong sản xuất. Đầu tiên, chúng ta chia sẻ một bộ nhớ đệm kernel (kernel cache) trên bốn đồ thị chỉ khác nhau về kích thước lô và đo thời gian mỗi lần xây dựng, để chúng ta có thể thấy các hình dạng sau đó tái sử dụng một kernel đã biên dịch thay vì phải trả chi phí JIT lần nữa. Sau đó, chúng ta ghi lại kế hoạch tích chập bên trong một CUDA graph, thiết lập luồng (stream) của cuDNN handle thành luồng ghi lại. Nhờ đó, công việc nằm trong đồ thị và chúng ta đo lường được lượng chi phí khởi chạy (launch overhead) trên mỗi lần lặp mà việc phát lại (replay) loại bỏ được.

Tóm lại, những gì chúng ta xây dựng ở đây tuy nhỏ về mã nguồn nhưng rộng về phạm vi: một phép tích chập, một phép nhân ma trận và một kernel attention, mỗi cái được biểu diễn dưới dạng đồ thị thay vì một lời gọi thư viện. Làm việc ở cấp độ đó đã thay đổi những gì chúng ta có thể quyết định. Chúng ta chọn những toán tử nào được gộp vào một kernel duy nhất, vì vậy các phép cộng bias, kích hoạt và giảm AMAX mà chúng ta đưa vào các phần kết thúc không bao giờ ghi dữ liệu trung gian ra bộ nhớ. Chúng ta tự chọn engine thay vì chấp nhận heuristic, và việc đo thời gian mọi cấu hình ứng viên cho chúng ta biết giá trị của lựa chọn đó. Chúng ta cũng chọn thời điểm trả chi phí biên dịch, đẩy nó ra khỏi đường dẫn nóng (hot path) bằng các kế hoạch đã tuần tự hóa, bộ nhớ đệm kernel chia sẻ giữa các hình dạng và ghi lại CUDA graph. Các kiểm tra so với PyTorch cũng quan trọng như việc đo thời gian, vì những nơi chúng ta chỉ đơn thuần khớp với nó thường là những nơi PyTorch đã gọi cuDNN bên dưới. Điều đó đánh dấu nơi API này phát huy giá trị: các hợp nhất không có tương đương ở cấp framework, các hình dạng đủ "nóng" để biện minh cho việc tự động tinh chỉnh và các kernel nhỏ nơi chi phí khởi động và khởi chạy chiếm ưu thế.

Xem TOÀN BỘ MÃ NGUỒN tại đây. Mọi tín dụng thuộc về nhà nghiên cứu của dự án này. Ngoài ra, hãy thoải mái theo dõi chúng tôi trên Twitter và đừng quên tham gia SubReddit 150k+ ML của chúng tôi và đăng ký nhận Bản tin. Khoan đã! bạn có dùng telegram không? bây giờ bạn cũng có thể tham gia cùng chúng tôi trên telegram.

Cần hợp tác với chúng tôi để quảng bá GitHub Repo HOẶC Trang Hugging Face HOẶC Ra mắt sản phẩm HOẶC Hội thảo trực tuyến, v.v.? Hãy kết nối với chúng tôi.

Sana Hassan, thực tập sinh tư vấn tại Marktechpost và là sinh viên văn bằng kép tại IIT Madras, rất đam mê việc ứng dụng công nghệ và AI để giải quyết các thách thức trong thế giới thực. Với sự quan tâm sâu sắc đến việc giải quyết các vấn đề thực tiễn, anh mang đến một góc nhìn mới mẻ cho sự giao thoa giữa AI và các giải pháp đời sống.

Đọc bài gốc

Bài viết được AI dịch và tổng hợp tự động từ MarkTechPost. Liên kết bài gốc ở phía trên. AIHOT.vn luôn dẫn nguồn đầy đủ — nếu bạn thấy điểm cần chỉnh sửa, hãy gửi ý kiến tại trang phản hồi.