Kiến trúc của GPU Tesla

Một phần của tài liệu Nghiên cứu một số thuật toán thám mã sử dụng công nghệ tính toán song song trên các bộ xử lý đồ họa (Trang 47)

Để tiến thêm một bƣớc xa hơn nữa, NVIDIA tung ra dòng sản phẩm có tên gọi Tesla, đây là dòng sản phẩm chuyên dụng dành cho cho tính toán hiệu năng cao. Kiến trúc tính toán và đồ họa hợp nhất Tesla đã phát triển, gia tăng đáng kể về số lƣợng so với phạm vi của các GPU đồ họa không lập trình đƣợc – khả năng xử lý các mảng dữ liệu ồ ạt đa tiến trình của nó dần trở thành một nền tảng hợp nhất hiệu năng cao cho cả những ứng dụng tính toán đồ họa và ứng dụng tính toán song song thông thƣờng trên GPU.

Bằng cách chia tỉ lệ số các processors (bộ xử lý) và các thành phần bộ nhớ, kiến trúc Tesla đã mở rộng một dải thị trƣờng các GPU bao gồm: những GPU có hiệu năng cao nhƣ GeForce 8800, những sản phẩm tính toán chuyên nghiệp nhƣ

47

Quadro, Tesla và những dòng card GeForce GPU chủ đạo đa dạng về giá cả. Những đặc trƣng tính toán của các dòng card này làm cho việc lập trình với các các lõi GPU trở nên dễ dàng hơn trong môi trƣờng lập trình C và với môi trƣờng CUDA. Và việc nó sẵn có trong các máy laptop, desktop, workstation, và server với môi trƣờng lập trình C và phần mềm CUDA, đã tạo nên những nền tảng kiến trúc siêu máy tính Tesla đầu tiên ở khắp mọi nơi.

Kiến trúc Tesla đƣợc xây dựng xung quanh một mảng có thể mở rộng của các SM (streaming multiprocessors – đa xử lý dòng lệnh). Những GPU hiện nay có thể thực hiện từ 768 cho tới 12288 tiến trình thực thi đồng thời. Việc mở rộng qui mô thực thi song song hóa một cách trong suốt trên một dải rộng (của các dòng GPU) sẽ là chìa khóa mục tiêu của cả kiến trúc GPU và mô hình lập trình CUDA (xem phần 4.3 dƣới đây).

Hình 4.3. Kiến trúc Tesla

Hình trên trình bày một GPU với 14 SMs - tƣơng đƣơng với 112 lõi SP (streaming processor), kết nối liên thông với 4 bộ nhớ DRAM bên ngoài. Khi một chƣơng trình CUDA trên host CPU gọi một kernel – grid kernel (C dành cho CUDA là mở rộng của C, nó cho phép ngƣời lập trình có thể định nghĩa các hàm C đƣợc gọi là nhân - kernel.

48

Và khi đƣợc gọi, nhân này sẽ đƣợc thực hiện N lần song song với nhau bởi N tiến trình CUDA (CUDA thread) khác nhau, trái ngƣợc với chỉ có một tiến trình đƣợc chạy nhƣ hàm C thông thƣờng), đơn vị phân phát công việc tính toán, gọi tắt là CWD (compute work distribution) sẽ liệt kê các block (block – khối, trong khối này có nhiều thread, và trong một grid thì có nhiều block) của lƣớivà bắt đầu phân phối các block này tới các SM với khả năng sẵn sàng thực thi (sẵn sàng chạy). Các tiến trình (thread) trong một block thread (block chứa nhiều thread) đƣợc thực hiện đồng thời trên một SM. Và khi các block thread kết thúc, đơn vị CWD sẽ thực thi các block mới trên các bộ xử lý trống, rảnh rỗi.

Một SM có chứa 8 lõi xử lý vô hƣớng SP – scalar processor (SP), 2 đơn vị chức năng đặc biệt – special function units (SFUs), dành cho tính toán siêu việt, 1 đơn vị dòng lệnh đa tiến trình – multithreaded instruction unit (MTIU) và 1 bộ nhớ chia sẻ trên nó (on-chip shared memory). SM đảm nhiệm việc tạo, quản lý, và thực hiện tới 768 tiến trình đồng thời trên phần cứng mà không tốn kém chi phí lập lịch. Nó có thể thực thi 8 block tiến trình CUDA nhiều lần và trong cùng một thời điểm, và bị giới hạn bởi số thread và các bộ nhớ tài nguyên. SM cung cấp rào chắn CUDA __syncthreads(), rào chắn này có tác dụng đồng bộ hóa bên trong SM với một lệnh đơn. Rào chắn nhanh chóng đồng bộ cùng nhau việc tạo các tiến trình thấp hơn và sắp xếp tiến trình một cách hiệu quả và không làm giảm hiệu năng, cho phép một thread mới đƣợc tạo để tính toán mỗi véc-tơ, điểm ảnh và con trỏ dữ liệu.

Để quản lý hàng trăm tiến trình chạy trong nhiều ứng dụng khác nhau, Tesla SM sử dụng một kiến trúc mới gọi là “đơn dòng lệnh đa tiến trình” – SIMT (single-instruction, multiple-thread). SM ánh xạ mỗi thread tới một lõi vô hƣớng SP, và mỗi một thread vô hƣớng sẽ chạy độc lập với nhau với địa chỉ tập lệnh và trạng thái thanh ghi riêng của nó. Đơn vị SM SIMT tạo, quản lý, sắp xếp và thi hành các thread trong một nhóm gồm 32 thread song song đƣợc gọi là warps. (Thuật ngữ này bắt nguồn từ dệt may, công nghệ song song các tiến trình đầu tiên). Các thread riêng lẻ săp xếp theo trật tự thành một SIMT warp khởi động cùng nhau tại cùng một địa chỉ chƣơng trình nhƣng tự do rẽ nhánh và thực thi một cách độc lập. Mỗi SM quản lý một nhóm gồm 24 warp với 32 thread trên mỗi warp, và tổng cộng có 768 thread.

Các lệnh đƣợc đƣa ra cùng một thời điểm, đơn vị SMIT sẽ lựa chọn một warp mà đã sẵn sàng thực hiện và đƣa ra lệnh tiếp theo tới các thread đã đƣợc chuẩn bị trong warp. Một warp thực thi một lệnh chung tại một thời điểm, do vậy hiệu quả cao nhất thu đƣợc khi cả 32 thread trong một warp phù hợp với nhau về đƣờng dẫn thực thi của chúng. Nếu các thread của một warp phân rẽ qua một dữ liệu phụ thuộc điều kiện rẽ nhánh, thì warp sẽ thi hành từng nhánh đƣờng dẫn lấy đƣợc, vô hiệu hóa các thread mà không ở trên

49

đƣờng dẫn đó, và khi tất cả các đƣờng dẫn đƣợc hoàn thành, các thread hội tụ trở lại để thực hiện trong cùng một đƣờng dẫn. Sự rẽ nhánh xảy ra chỉ trong một warp; các warp khác nhau thực thi độc lập với nhau và không chú ý tới chúng đang thực thi một đƣờng dẫn chung hoặc đƣờng dẫn đoạn code rời rạc. Và kết quả là, kiến trúc GPU Tesla đột nhiên trở nên mềm dẻo và hiệu quả hơn trên các nhánh code so với các GPU trƣớc đây, nhƣ các warp 32 thread của chúng bị hạn chế nhiều hơn so với độ rộng SIMD (single- instruction multiple-data) của các GPU trƣớc kia.

Kiến trúc SIMT gần giống với cấu trúc véc-tơ SIMD mà trong đó một lệnh điều khiển nhiều quá trình xử lý các phần tử. Một điều then chốt khác là cấu trúc véc-tơ SIMD bộc lộ độ rộng với phần mềm, trong khi các lệnh SIMT định rõ việc thực thi và trạng thái phân nhánh của một thread đơn. Trong sự trái ngƣợc với các máy véc-tơ SIMD, SIMT cho phép lập trình viên viết đoạn code song song phân cấp các thread một cách độc lập, các thread vô hƣớng, cũng nhƣ là code song song dữ liệu dành cho phối hợp các thread. Dành cho các mục đích đúng đắn, lập trình viên có thể về căn bản lờ đi động thái SIMT; tuy nhiên, chắc chắn hiệu năng có thể nhận thấy bởi việc chăm sóc đoạn code ít khi yêu cầu các thread trong warp phân rẽ.

Trong thực tiễn, nó tƣơng tự nhƣ vai trò của các dòng đệm trong code truyền thống: kích thƣớc dòng đệm có thể lờ đi một cách an toàn khi thiết kế với tính chất đúng đắn nhƣng phải đƣợc xem xét dƣới cấu trúc đoạn code khi thiết kế cho hiệu năng tối đa. Các kiến trúc véc-tơ, trên một hƣớng khác, yêu cầu phần mềm kết hợp một khối để load trong các véc-tơ và điều khiển sự phân rẽ một cách thủ công.

Một biến thread thƣờng cƣ trú trong các thanh ghi đang sống. Bộ nhớ chia sẻ 16Kb SM có độ trễ truy cập thấp và băng thông cao tƣơng tự nhƣ bộ nhớ đệm L1; nó giữ các biến CUDA __shared__ trên block để dành cho việc kích hoạt các block thread. SM cung cấp việc nạp/lƣu trữ các lệnh để truy cập biến CUDA __device__ trên GPU có bộ nhớ DRAM gắn ngoài. Nó kết hợp thành một khối truy cập riêng lẻ của các thread song song trong cùng một warp và ở trong vài truy cập block bộ nhớ khi các địa chỉ rơi vào trong cùng một block và gặp các tiêu chuẩn liên kết. Bởi vì việc trễ của bộ nhớ chung có thể tới hàng trăm xung nhịp xử lý, các chƣơng trình CUDA copy dữ liệu tới bộ nhớ chia sẻ khi nó phải đƣợc truy cập rất nhiều lần bởi một block thread. Tesla nạp/lƣu trữ các lệnh bộ nhớ sử dụng địa chỉ là các byte số nguyên để thuận tiện cho trình biên dịch chuyển đổi tối ƣu các đoạn code. Tiến trình lớn đếm trong mỗi SM, cũng nhƣ là hỗ trợ cho nhiều yêu cầu nạp còn tồn tại, giúp đỡ che phủ độ trễ trong việc nạp và sử dụng tới các bộ nhớ DRAM gắn ngoài. Kiến trúc GPU Tesla mới nhất cũng cung cấp các lệnh bộ nhớ đọc- sửa-ghi nguyên tử, thuận tiện cho sự giảm bớt song song và quản lý cấu trúc song song dữ liệu.

50

Các ứng dụng CUDA thực hiện tốt trên các GPU kiến trúc Tesla bởi vì mô hình song song CUDA, sự đồng bộ, các bộ nhớ chia sẻ, và phân cấp của các nhóm thread ánh xạ hiệu quả tới các chức năng của kiến trúc GPU, và cũng bởi vì CUDA cũng là mô hình ứng dụng song song nhanh nhất.

4.3.Môi trường phát triển ứng dụng cho GPU – CUDA

Vào tháng 11/2006, hãng NVIDIA phát hành CUDATM, một kiến trúc tính toán song song đa dụng – với một mô hình lập trình song song và kiến trúc tập lệnh mới – có thể thúc đẩy động cơ (engine) tính toán song song trong các GPU của NVIDIA để giải quyết nhiều bài toán tính toán phức tạp theo một hƣớng hiệu năng hơn nhiều so với CPU.

CUDA đi cùng với một môi trƣờng phần mềm cho phép ngƣời phát triển sử dụng C nhƣ ngôn ngữ lập trình cấp cao. Nhƣ minh họa ở hình 4-4, các ngôn ngữ hoặc các giao diện lập trình ứng dụng khác đƣợc hỗ trợ, nhƣ CUDA FORTRAN, OpenCL, và DirectCompute.

Hình 4.4. CUDA được thiết kế để hỗ trợ nhiều ngôn ngữ hoặc các API khác nhau.

4.3.1.Khả năng mở rộng của CUDA

Sự xuất hiện của các CPU đa lõi và GPU nhiều lõi cho thấy rằng xu thế chủ đạo của các chip xử lý bây giờ là các hệ thống song song. Hơn nữa, đặc tính song song của chúng tiếp

51

tục gia tăng theo luật của Moore. Sự thách thức là để phát triển phần mềm ứng dụng mở rộng (scale) trong suốt về tính song song để tận dụng đƣợc sự gia tăng về số lƣợng của các lõi xử lý, càng có nhiều các ứng dụng 3D mở rộng (scale) một cách trong suốt tính song song của nó tới các GPU nhiều lõi với các số lƣợng thay đổi lớn của các lõi.

Mô hình lập trình CUDA đƣợc thiết kế để vƣợt qua thách thức này trong khi vẫn duy trì một độ khó về học tập thấp cho các ngƣời lập trình quen thuộc với ngôn ngữ lập trình chuẩn nhƣ C.

Trong nhân của CUDA là ba khái niệm trừu tƣợng chính – một cơ chế phân cấp của các nhóm thread, các bộ nhớ chia sẻ, và rào chắn đồng bộ – đơn giản đƣợc bộc lộ tới ngƣời lập trình nhƣ là một tập nhỏ các mở rộng về ngôn ngữ.

Các khái niệm trừu tƣợng này cung cấp cơ chế song song dữ liệu và song song tiến trình nhỏ đƣợc lồng bên trong song song dữ liệu và song song tác vụ lớn. Chúng hƣớng ngƣời lập trình phân chia bài toán thành các bài toán con thô có thể đƣợc giải quyết độc lập một cách song song bởi các block gồm các tiến trình và mỗi bài toán con thành các phần mịn hơn mà có thể đƣợc giải quyết cùng nhau theo song song bởi tất cả các tiến trình bên trong block đó. Sự phân chia này biểu hiện khả năng của ngôn ngữ đó là cho phép các tiến trình phối hợp với nhau để xử lý mỗi bài toán con, và tại cùng một thời điểm cho phép mở rộng một cách tự động. Quả thực, mỗi block tiến trình có thể đƣợc lập lịch trên bất kỳ số lƣợng lõi xử lý nào đang có, theo bất cứ thứ tự nào, đồng thời hay là tuần tự, do đó một chƣơng trình CUDA đã biên dịch có thể chạy trên mọi số lƣợng lõi xử lý nhƣ đƣợc minh họa ở hình 4-5, và chỉ vào lúc chạy hệ thống mới cần biết số lƣợng lõi xử lý vật lý.

Mô hình lập trình có thể mở rộng này cho phép kiến trúc CUDA mở rộng một phạm vi thƣơng mại rộng lớn bằng cách là đơn giản mở rộng số lƣợng lõi xử lý và các phân vùng bộ nhớ: từ các GPU dành cho tính toán hiệu năng cao GeForce và các sản phẩm cao cấp Quadro và tính toán Tesla tới một số đa dạng các loại rẻ hơn, dòng chính là các GPU GeForce.

Một chƣơng trình đa luồng đƣợc phân chia thành các block tiến trình và chúng đƣợc thực thi độc lập với nhau, do đó một GPU nhiều lõi sẽ tự động thực hiện chƣơng trình đó nhanh hơn một GPU có ít lõi hơn.

52

Hình 4.5. Khả năng tự mở rộng

4.3.2.Các khái niệm chính

Phần này giới thiệu các khái niệm chính của mô hình lập trình CUDA bằng cách tóm tắt cách chúng đƣợc mở rộng từ ngôn ngữ C.

53

4.3.2.1.Kernel

CUDA C mở rộng ngôn ngữ C bằng cách cho phép ngƣời lập trình định nghĩa các hàm của C, đƣợc gọi là các kernel, mà khi đƣợc gọi, sẽ đƣợc thực hiện N lần theo song song bởi N thread CUDA, ngƣợc với chỉ một lần nhƣ các hàm thông thƣờng của C.

Một kernel đƣợc định nghĩa sử dụng đặc tả khai báo __global__ và số tiến trình CUDA thực hiện kernel đó trong một lần gọi kernel đƣợc chỉ ra bằng cách dùng một cú pháp cấu hình sự thực thi mới <<<...>>> Mỗi tiến trình thực thi kernel đƣợc gán một ID tiến trình duy nhất mà có thể đƣợc truy cập bên trong kernel bằng biến có sẵn threadIdx.

Để minh họa, đoạn mã ví dụ mẫu sau đây thực hiện cộng hai vector A và B có kích thƣớc N và lƣu kết quả vào vector C:

// Kernel definition

__global__ void VecAdd(float* A, float* B, float* C) {

int i = threadIdx.x; C[i] = A[i] + B[i]; }

int main() {

...

// Kernel invocation with N threads

VecAdd<<<1, N>>>(A, B, C); }

Ở đây, mỗi tiến trình trong N tiến trình thực thi hàm VecAdd() thực hiện phép cộng cho một cặp.

4.3.2.2.Phân cấp thread

Theo quy ƣớc, biến threadIdx là một vector có 3 thành phần, do đó các thread có thể đƣợc định danh sử dụng một chỉ số thread một chiều, hai chiều, hoặc ba chiều, để tạo thành block một chiều, hai chiều, hoặc ba chiều. Cơ chế này cung cấp một cách tự nhiên

54

để thực hiện tính toán trên các phần tử trong một trƣờng nhƣ một vector, ma trận, hoặc khối.

Chỉ số của một thread và ID của nó liên hệ với nhau rất rõ ràng: Với một block một chiều, chúng là nhƣ nhau; với một block hai chiều có kích thƣớc (Dx ,Dy), ID của một thread có chỉ số (x,y) là (x + yDx); với một block ba chiều có kích thƣớc (Dx ,Dy ,Dz), ID của một thread có chỉ số (x,y,z) là (x + yDx + z Dy Dz).

Ví dụ, đoạn mã sau đây cộng hai ma trận AB có kích thƣớc NxN và lƣu kết quả vào ma trận C :

// Kernel definition

__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N])

{

int i = threadIdx.x; int j = threadIdx.y; C[i][j] = A[i][j] + B[i][j]; }

int main() {

...

// Kernel invocation with one block of N * N * 1 threads

int numBlocks = 1;

dim3 threadsPerBlock(N, N);

MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C); }

Có sự giới hạn về số thread trong một block, bởi vì tất cả thread của một block đƣợc dự kiến ở trên cùng lõi xử lý và phải chia sẻ các tài nguyên bộ nhớ giới hạn cho lõi đó. Trên các GPU hiện nay, một block có thể chứa đƣợc tới 512 thread.

Tuy nhiên, một kernel có thể đƣợc thực hiện bởi nhiều block giống nhau, nên tổng số thread sẽ đƣợc tính bằng số thread trong một block nhân với số block.

55

Các block đƣợc tổ chức thành một grid (lƣới) một chiều hoặc hai chiều của các block nhƣ minh họa ở hình 4.5. Số block trong một grid thƣờng dựa vào kích thƣớc của dữ liệu cần xử lý hoặc số lõi trong hệ thống (mà có thể tăng rất nhanh).

Số thread trong một block và số block trong một grid đƣợc chỉ ra trong cú pháp

Một phần của tài liệu Nghiên cứu một số thuật toán thám mã sử dụng công nghệ tính toán song song trên các bộ xử lý đồ họa (Trang 47)