Warp Scheduler: Giải Thích Khái Niệm Latency Hiding Trong CUDA
Trong bài viết trước về Synchronization - Asynchronization, mình đã đề cập đến khái niệm latency hiding. Đây là một trong những khái niệm quan trọng khi làm việc với CUDA. Latency hiding đề cập đến nguyên tắc luôn giữ cho thread bận rộn, vì vậy, trong bài này, mình sẽ giải thích rõ hơn về cơ chế này cũng như cách hoạt động của nó. Đây là một phần không thể thiếu trong việc tối ưu hóa mã nguồn của chúng ta.
Lưu ý: Bài viết này nhằm mục đích giúp các bạn hiểu rõ hơn về cơ chế hoạt động của CUDA. Nếu bạn chỉ quan tâm đến việc lập trình CUDA ở mức độ cơ bản, có thể bỏ qua bài viết này.
Giới Thiệu Warp Scheduler
Để dễ hình dung hơn về Warp Scheduler, mình sẽ đưa ra một ví dụ từ cuộc sống hàng ngày.
Giả sử có 100 người đến bưu điện để gửi hàng, nhưng chỉ có một nhân viên làm việc. Để gửi hàng thành công, mỗi người cần thực hiện hai bước:
- Điền thông tin gửi hàng (quá trình tốn nhiều thời gian).
- Nhân viên xác nhận đơn và thực hiện thủ tục gửi hàng (quá trình nhanh).
Trong trường hợp này, thay vì nhân viên chờ từng người làm xong, họ có thể phát đơn cho người tiếp theo và khi người đó hoàn thành, họ sẽ quay lại nhanh chóng để gửi hàng. Phương pháp này giúp tiết kiệm thời gian rất nhiều.
Tương tự, trong máy tính, khi xử lý một biểu thức như y[i] += x[i] * 3
, cũng có hai bước chính:
- Lệnh truy cập bộ nhớ: Thời gian từ khi lệnh load/store được đưa ra cho đến khi dữ liệu đến đích.
- Lệnh số học: Thời gian mà một phép toán bắt đầu cho đến khi có đầu ra.
Mình sẽ giải thích rằng trong trường hợp của y[i] += x[i] * 3
, thay vì máy tính chờ load/store x[0]
và y[0]
, nó sẽ chuyển sang xử lý x[1]
và y[1]
trong khi x[0]
và y[0]
đang được load/store. Đây là một kỹ thuật giúp tăng hiệu suất xử lý.
Cách hoạt động của Warp Scheduler
- Đặt vấn đề: Warp Scheduler sẽ giúp hoán đổi các warp đang bận để tiết kiệm thời gian. Do đó, nó còn được gọi là latency hiding hoặc luôn giữ cho thread bận rộn (số lượng warp mà mỗi Warp Scheduler có thể điều khiển tùy thuộc vào cấu hình máy).
Khi nói đến warp, chúng ta thường thấy ba trạng thái:
- stalled: warp đang thực hiện một nhiệm vụ.
- eligible: warp đang sẵn sàng để tham gia.
- selected: warp được chọn để thực thi.
Khi warp được chọn (selected), nó sẽ thực hiện lệnh truy cập bộ nhớ. Trong thời gian chờ, warp sẽ được hoán đổi cho một warp khác. Có hai tình huống có thể xảy ra: warp tiếp theo có thể là stalled hoặc eligible. Nếu warp tiếp theo là eligible, điều đó thật tuyệt vời. Ngược lại, nếu là stalled, nó sẽ được hoán đổi cho đến khi gặp một warp eligible.
Câu hỏi quan trọng
Nếu vậy, việc tạo ra nhiều warp có giúp tăng số lượng warp eligible không?
Câu trả lời là không. Việc tạo ra quá nhiều warp đồng nghĩa với việc warp scheduler phải thực hiện nhiều hơn. Khi số lượng thread tăng lên, số lượng register dành cho mỗi thread sẽ giảm, dẫn đến việc SM chạy chậm hơn. Do đó, chúng ta cần cân nhắc số lượng thread nên sử dụng là bao nhiêu cho phù hợp.
Ví dụ, nếu có 128 người đến gửi thư, thì không thể sử dụng 128 nhân viên, cũng như nếu chúng ta cần xử lý một mảng 128 phần tử mà chỉ dùng 128 thread (tương đương 4 warp) là một quyết định sai lầm. Việc này không chỉ tốn tài nguyên mà còn làm giảm hiệu suất vì một nhân viên có thể xử lý nhiều hơn một người.
Theo thói quen của việc tối ưu hóa, khi bạn phân tích các đoạn mã CUDA trong OpenCV bằng nsight system, bạn sẽ thấy rằng họ thường sử dụng rất ít thread.
Ví dụ mã code
Dưới đây là một ví dụ sử dụng thư viện OpenCV CUDA để cộng hai bức ảnh:
cpp
#include "opencv2/opencv.hpp"
#include <opencv2/cudaarithm.hpp>
cv::Mat opencv_add(const cv::Mat &img1, const cv::Mat &img2) {
cv::cuda::GpuMat d_img1, d_img2, d_result;
d_img1.upload(img1);
d_img2.upload(img2);
cv::cuda::add(d_img1, d_img2, d_result);
cv::Mat result;
d_result.download(result);
return result;
}
int main() {
cv::Mat img1 = cv::imread("circles.png");
cv::Mat img2 = cv::imread("cameraman.png");
cv::Mat result = opencv_add(img1, img2);
cv::imshow("Result", result);
cv::waitKey();
return 0;
}
Bạn có thể phân tích kernel bằng lệnh sau:
nsys profile -o test ./a.out
Kết luận
Vậy chúng ta nên sử dụng bao nhiêu thread cho phù hợp?
Câu trả lời phụ thuộc vào cấu hình của từng máy tính cụ thể cũng như nguyên nhân gây ra tình trạng warp stalled. Trong bài viết tiếp theo, mình sẽ phân tích các nguyên nhân khiến warp bị stalled và cách xác định số lượng thread phù hợp nhất.
source: viblo