Mô Hình Lập Trình Song Song CUDA 2 8-

Một phần của tài liệu Ứng dụng công nghệ tính toán đa dụng trên các bộ xử lý đồ họa đa lõi của NVIDIA trong bài toán mã hóa âm thanh chuẩn AAC (Trang 28 - 35)

1. 4.Nén 15

2.3. Mô Hình Lập Trình Song Song CUDA 2 8-

CUDA là một trong những mở rộng của ngôn ngữ lập trình C và C++. Lập trình viên viết một chương và gọi đó là các nhân song song (kernel), cái mà có thể là một hàm đơn giản hay là một chương trình đầy đủ. Một nhân được thực hiện song song thông qua một tập hợp các thread song song. Người lập trình có thể tổ chức những thread này trong một lưới (grid) phân cấp của các thread block. Một

thread block là một tập hợp của các tiến trình đồng thời mà có thể hợp tác giữu chúng với nhau thông qua một rào chắn đồng bộ và chia sẻ truy cập tới một phân vùng nhớ riêng tư trên block đó. Một lưới – grid là tập hợp của các thread block mà

có thể thực thi độc lập và theo cách đó nó có thể thực thi song song.

Khi gọi một nhân, lập trình viên phải định rõ số thread trên mỗi block và số block được tạo lên grid. Mỗi thread đưa cho một số thread ID tĩnh threadIdx trong mỗi thread block của nó, các số được đánh là 0, 1, 2, …, blockDim-1, và mỗi thread block đưa ra một số block ID tĩnh blockIdx trong lưới của nó. CUDA hỗ trợ các thread block chứa tới 512 thread trong một block. Và để thuận lợi, các thread block và các grid có thể có một, hai hoặc ba chiều, được truy cập thông qua các trường chỉ số .x, .y, .z.

Như một ví dụ đơn giản cho lập trình song song, giả sử rằng chúng ta cho hai véc-tơ xy với n sốthực dấu phẩy động mỗi véc-tơ và cho rằng chúng ta tính toán ra kết quảcủa yax +y, cho một vài giá trị vô hướng a. Lấy ví dụ hàm saxpy được định nghĩa bởi thư viện BLAS(basic linear algebra subprograms). Đoạn mã thực hiện sự tính toán này trên cả bộ xử lý tuần tự và song song sử dụng CUDA được minh họa trong hình 25:

Hình 2.4. Đối chiếu hai đoạn mã x lý tun t và song song

Khai báo định rõ __global__ chỉ ra cho chúng ta biết rằng nó là thủ tục để bắt đầu vào một kernel. Các chương trình CUDA thực thi các nhân song song với cú pháp gọi hàm mở rộng như sau:

kernel<<<dimGrid, dimBlock>>>(…parameter list …);

khi dimGrid and dimBlock là ba phần tử véc-tơ có kiểu dim3, và định rõ số chiều của grid trong các block và số chiều của các block trong các thread, tách biệt hẳn ra. Nếu không nói rõ thì số chiều mặc định là 1.

Ví dụ, chúng ta chạy một lưới mà gán một thread tới mỗi phần tử của véc-tơ và đặt 256 thread trong mỗi block. Mỗi thread tính toán một chỉ số phần tử từ thread của nó và các block ID và sau đó sẽ thực hiện yêu cầu tính toán trên phần tử véc-tơ tương ứng. Phiên bản tuần tự và song song của đoạn code này là giống nhau. Đoạn mã tuần tự có chứa một vòng lặp, nơi mà mỗi sự lặp lại là độc lập với tất cả vòng lặp khác. Các vòng lặp có thể được biến đổi một cách máy móc trong các kernel song song: mỗi vòng lặp trở thành một thread độc lập. Bằng cách gán một thread đơn cho mỗi phần tử đầu ra, chúng ta ngăn ngừa sự đồng bộ giữa các thread khi ghi kết quả vào bộ nhớ.Đoạn chương trình của một kernel CUDA đơn giản là một hàm C cho một thread liên tục.

Theo cách như vậy nó trở nên dễ hiểu để viết chương trình và đơn giản hơn so với code song song dành cho các thao tác véc-tơ. Mô hình song song được xác định sáng sủa và rõ ràng bởi việc định rõ các chiều của một grid và các thread bock của nó khi chạy một kernel.

Việc thực thi song song và quản lý các thread đều hoàn toàn tự động. Tất cả các thread được tạo, sắp xếp, và kết thúc được xử lý cho các lập trình viên bởi một

hệ thống cơ sở nằm ở mức dưới. Thực vậy, một kiến trúc GPU Tesla thực hiện quản lý tất cả các thread độc lập trong một phần cứng. Các thread trong một block thực thi đồng thời và có thể đồng bộ tại một rào chắn bằng cách gọi hàm __syncthreads() bên trong. Sau khi đi qua rào chắn, các thread này cũng được đảm bảo để cho các thread tham gia trước rào chắn xem thấy chúng ghi vào trong bộ nhớ. Điều này đảm bảo rằng không có thread nào tham gia vào rào chắn có thể tiến hành cho tới khi tất cả các thread tham gia đạt được tới rào chắn. Như vậy, các thread trong một block có thể kết nối với mỗi thread khác bằng cách ghi hoặc đọc trên bộ nhớ chia sẻ của mỗi block tại một rào chắn đồng bộ.

Từ lúc các thread trong một block có thể chia sẻ bộ nhớ cục bộ và đồng bộ thông qua các rào chắn, chúng sẽ cư trú trên những bộ xử lý vật lý hoặc các multiprocessor giống nhau. Tuy nhiên, số thread block có thể là rất lớn so với số lượng các processor. Điều này thực chất là xử lý các phần tử và đưa cho lập trình khả năng mềm dẻo trong việc song song hóa tại các điểm nào là thuận tiện nhất. Điều này cũng cho phép xem xét phân tích trực quan vấn đề, như là số block có thể được ra lệnh bởi kích thước của dữ liệu đang đươc xử lý hơn là bởi số bộ xử lý trong hệ thống. Nó cũng cho phép cùng một chương trình CUDA co dãn rộng rãi hơn hay thay đổi số lõi bộ xử lý.

Để quản lý việc xử lý phần thực sự và cung cấp khả năng mở rộng, CUDA yêu cầu các thread block tiến hành một cách độc lập. Nó phải được thi hành trên các block theo nhiều kiểu, song song hay trong tuần tự. Các block khác nhau không có nghĩa là kết nối độc lập với nhau, mặc dù chúng có thể phối hợp các hoạt động của chúng sử dụng các thao tác ở bộ nhớ nguyên tử trên bộ nhớ dùng chung của các thread – như việc gia tăng nguyên tử các con trỏ hàng đợi là một ví dụ.

Sự độc lập này yêu cầu phải cho phép các thread block được sắp xếp trong bất cứ thứ tự nào thông qua số các lõi, việc tạo mô hình CUDA có thể mở rộng thông qua một số tùy ý số lượng các lõi, chẳng khác gì thông qua một sự đa dạng của các kiến trúc song song. Nó cũng giúp tránh xa khả năng “đồng hồ chết” (tức là một

Các thread có thể truy cập dữ liệu từ nhiều phân vùng bộ nhớ trong khi chúng thi hành. Mỗi thread có một phân vùng bộ nhớ riêng (private local memory). CUDA sử dụng bộ nhớ này cho các biến private-thread mà không làm lấp đầy các thanh ghi của thread, cũng giống như là cho các khung stack và các dãy thanh ghi. Mỗi thread block có một bộ nhớ chia sẻ mà có thể thấy được bởi tất cả các thread block khác mà có cùng thời gian tồn tại như block. Cuối cùng, toàn bộ các thread truy cập vào cùng một bộ nhớ dùng chung. Các chương trình khai báo các biến trong bộ nhớ chia sẻ và dùng chung với từ khóa kiểu __shared__ và __device__. Trên một kiến trúc Tesla GPU, những phân vùng bộ nhớ này phù hợp với các bộ nhớ vật lý riêng biệt: bộ nhớ chia sẻ trên mỗi block là RAM on-chip có độ trễ thấp, trong khi bộ nhớ dùng chung lại ở trong DRAM trên card đồ họa.

Bộ nhớ chia sẻ được mở rộng thành bộ nhớ có độ trễ thấp gần mỗi bộ xử lý, chúng giống như một bộ đệm L1 (cache). Bởi vậy, nó có thể cung cấp một kết nối có hiệu suất cao và chia sẻ dữ liệu giữa các thread trong một block. Từ lúc nó có cùng thời gian tồn tại như các thread block tương ứng của nó, thì code kernel sẽ thường khởi tạo dữ liệu trong các biến chia sẻ, tính toán và sử dụng các biến chia sẻ đó, và copy kết quả từ bộ nhớ chia sẻ tới bộ nhớ dùng chung. Các thread block của các lưới liên tục kết nối với nhau qua bộ nhớ dùng chung và sử dụng nó để đọc đầu vào và ghi kết quả.

Biểu đồ hình 26 thể hiện các tầng lồng nhau của các thread, các block thread, và các lưới block. Nó cho thấy các tầng tương ứng của bộ nhớ chia sẻ: bộ nhớ cục bộ, chia sẻ, và bộ nhớ dùng chung trên thread, trên block thread, và trên các ứng dụng chia sẻ dữ liệu.

Hình 2.5: Phân cp b nh

Một chương trình quản lý phân vùng bộ nhớ dùng chung có thể thấy được bởi các kernel thông qua gọi các hàm CUDA thời gian thực, như là cudaMalloc() và

cudaFree(). Các kernel có thể thực hiện trên các thiết bị vật lý riêng biệt, như là sự

lựa chọn khi chạy các kernel trên GPU. Do đó, ứng dụng phải sử dụng hàm

cudaMemcpy() để copy dữ liệu giữa các phân vùng định sẵn và trên bộ nhớ chủ hệ

thống (host system memory).

Mô hình lập trình CUDA là gần giống với kiểu của họ mô hình SPMD (single- program multiple-data) – nó biểu diễn mô hình song song một cách nhanh chóng, rõ ràng, mỗi kernel chạy trên một số thread cố định. Tuy nhiên, CUDA mềm dẻo hơn so với sự thi hành của SPMD, bởi vì mỗi kernel được gọi tự động tạo một lưới mới

trình viên có thể sử dụng một mức độ chuyển đổi mô hình song song cho mỗi kernel, hơn là thiết kế toàn bộ các phương diện của tính toán để sử dụng của cùng một số thread

Hình 2.6 thể hiện một ví dụ của một SPMD giống như đoạn mã trình tự CUDA. Đầu tiên nó thể hiện kernelF trên một lưới hai chiều của block có kích

thước 3x2, nơi mà mỗi block thread hai chiều chứa các thread kích cỡ 5x3. Sau đó nó thể hiện kernelG trên một lưới một chiều của 4 block thread một chiều với 6 thread mỗi block. Bởi vì kernelG phụ thuộc vào kết quả của kernelF, chúng được

tách biệt bởi một rào chắn đồng bộ liên kết các nhân.

Các thread đồng thời trên một block biểu thị mô hình song song dữ liệu và thread mịn. Các block độc lập trong một grid thì biểu thị mô hình song song dữ liệu thô. Các grid độc lập biểu thị mô hình song song thao tác thô. Một kernel là đoạn code C đơn giản cho một thread trong tầng.

Một phần của tài liệu Ứng dụng công nghệ tính toán đa dụng trên các bộ xử lý đồ họa đa lõi của NVIDIA trong bài toán mã hóa âm thanh chuẩn AAC (Trang 28 - 35)

Tải bản đầy đủ (PDF)

(68 trang)