Vietnamen’s Weblog

Time, Chances, Diligence, Intelligence: which is the most important?

Phần 1: CUDA programming model

leave a comment »

Như đã giới thiệu ở bài viết trước, Tesla là thế hệ GPGPU card của NVIDIA cung cấp CUDA – một tập các lệnh mở rộng của ngôn ngữ C viết theo chuẩn (industry-standard C) giúp cho việc viết các chương trình song song. Vậy thế nào là lập trình song song và kiến trúc phần cứng để hỗ trợ cho nó như thế nào? – Thông tin chi tiết có thể tham khảo ở trang này.

CPU architectures?

Mọi quá trình đều tồn tại cái gọi là “atom”, nghĩa là đơn vị nhỏ nhất không thể chia nhỏ. Trong lập trình máy tính, ban đầu, “atom” chính là các dòng mã máy. Với 1 chương trình được viết bởi 1 ngôn ngữ nào đó, để có thể thực thi, cần được biên dịch ra mã máy gồm các chuỗi nhị phân  (0 và 1) có chiều dài xác định; máy tính, cụ thể là bộ vi xử lí trung tâm (CPU – Central Processing Unit), sẽ tuần tự đọc từng dòng mã máy để thực hiện toàn bộ chương trình. CPU lấy kiến trúc của Von Neumann làm nền tảng.

Von_Neumann_architecture

Kiến trúc CPU của Von Neumann

Theo hình trên, ALU (Arithmetic Logic Unit) là nơi trực tiếp xử lí tác vụ được lấy từ mã máy. Một mã máy là một chuỗi bit nhị phân (0,1) với chiều dài xác định (8-bit, 32-bit, 64-bit – thế hệ càng về sau thì số bít càng cao) chia làm nhiều phần.

– phần đầu: chứa thông tin tác vụ cần thực hiện (op code), ví dụ: add, sub, or, xor, not, and…

– phần tiếp theo chứa địa chỉ ô nhớ nơi lưu trữ dữ liệu mà op code đó tác động lên

– …

Khi chạy 1 chương trình đã được biện dịch, phần mã máy sẽ nạp vào bộ nhớ – có tên gọi là bộ nhớ chương trình (program memory, nằm trong RAM). Sau đó, CPU sẽ thực hiện các bước

  1. đọc từng dòng lệnh (fetch),
  2. giãi mã nội dung dòng lệnh (decode),
  3. thực thi lệnh (execute), và
  4. ghi lại kết quả (writeback).

Nếu chia quá trình thực thi 1 lệnh ra các bước nhỏ hơn (step) như ở trên: fetch, decode, execute, writeback – “atom” bây giờ là 1 step, lệnh thứ (n) thực thi xong step 1 thì lệnh (n+1) có thể thực thi step 1, không cần đợi lệnh (n) thực thi tới hết step 4 như kiến trúc cũ. Điều này làm tăng tốc độ xử lí lên – đây chính là cơ chế đường ống (pipeline). Tuy vậy, cơ chế pipeline cũng không thể giúp cho cùng 1 step của 2 lệnh khác nhau chạy đồng thời. Đồng thời, mỗi lệnh máy cũng chỉ tác dụng lên 1 địa chỉ ô nhớ. Các CPU dạng này gọi là scalar processor, nó thuộc kiến trúc SISD (single-instruction, single-data).

Vì thời gian lấy lệnh (fetch) thường là khá lâu – do thời gian đọc từ RAM chậm, giải pháp bộ nhớ đệm (cache memory: L1, L2) với khả năng truy cập rất nhanh từ CPU đã ra đời để giúp lưu sẵn 1 loạt các lệnh trong cache memory, điều này giảm thời gian lấy lệnh (fetch) từ bộ nhớ chương trình.

Trong thực tế, nhu cầu xử lí nhiều dữ liệu cùng lúc, với cùng 1 tác vụ là rất phổ biến, ví dụ: nhân một ma trận cho 1 giá trị, như vậy mọi phần tử của ma trận đều được thực hiện cùng 1 tác vụ nhân với cùng 1 số. Vì đơn vị thực hiện tính toán trong CPU chính là ALU (arithmetic logic unit), việc tăng số lượng ALU trên mỗi CPU có thể giúp khả năng xử lí nhiều data cùng lúc. Đồng thời, các dòng lệnh máy thay vì chứa thông tin 1 địa chỉ ô nhớ riêng lẽ, sẽ chứa 1 vùng địa chỉ ô nhớ hay một tập hợp các địa chỉ ô nhớ. Các CPU dạng này gọi là vector processor (array processor), nó thuộc kiến trúc SIMD (single-instruction, multiple data).

Sau này thì có kiến trúc multicore (dual-core, quad-core) nằm trên cùng 1 dye.

dual_core

Dual core

Còn nhiều kiến trúc nữa nhưng ta không thể nói hết ở đây. Ngoài các bộ vi xử lí đa dụng như vậy, các dòng vi xử lí cho đồ họa cũng từng bước ra đời – gọi là GPU (Graphics Processing Unit). Trước khi nói đến GPU, ta giới thiệu sơ về threads. Một thread là một bản sao (hiện thực – instances) của một đoạn code mà được thực thi như 1 đơn vị – i.e. thực hiện từ đầu đến cuối mà không bị can thiệp. Để định danh một threads, hệ thống cần 1 hoặc nhiều thông tin (contextual information). Tùy vào đó, mà thời gian để chuyển từ thread này sang thread kia có thể nhanh (vài chục microseconds) hoặc chậm (vài ngàn microseconds). Qua đó, threads được phân loại là: heavy weight, middleweight, và lightweight.

  • heavy weight: để xác định 1 thread cần rất nhiều thông tin: hardware register, kernel stack, user-level stack, …  (Unix process)
  • middle weight: các thread chỉ nằm trong 1 không gian địa chỉ nên chỉ cần 1 thông tin để xác định ra thread
  • light weight: thread loại này không cần vùng ô nhớ riêng, chúng chia sẻ với các threads khác trong cùng 1 process nên việc chuyển đổi qua lại giữa các thread rất nhanh.

GPU architecture?

GPU là các bộ vi xử lí với các opcode được thiết kể chủ yếu cho các tác vụ đồ họa. GPU có mặt ở khắp nơi, trong các hệ thống nhúng, điện thoại di động, máy tính để bàn và các thiết bị trò chơi. Các lệnh cho 2D thông thường trong đồ họa như: BitBLT (kết hợp nhiều ảnh theo 1 toán tử nào đó – Raster operation (OR, AND, XOR…) – để cho ra ảnh kết quả), các lệnh để vẽ các hình cơ bản – primitives (chữ nhật, hình tam giác, hình tròn, hình cung). Các GPU sau này có hỗ trợ thêm cho các lệnh 3D.

Các lệnh mà các GPU cung cấp là ngôn ngữ cấp thấp (assembly language). Để tạo điều kiện cho các nhà lập trình sử dụng, các thư viện viết cho các ngôn ngữ cấp cao (C/C++…) ra đời.

  • 2D: WinG, DirectDraw, OpenGL
  • 3D: Direct3D, OpenGL

Với các thế hệ GPU về sau, nhiều lệnh phức tạp cũng được thêm vào, e.g. để thực hiện các tác vụ: H&T (hardware transform & lightning), pixel shader (fragment shader), và vertex shader. Thuật ngữ shader dùng để chỉ (1) một chương trình máy tính chạy trên một môi trường đặc biệt để (ví dụ: GPU), (2) tập hợp các lệnh trong GPU dùng để tính toán các hiệu ứng đổ bóng với độ chính xác cao. Vì yêu cầu cần thực hiện các tác vụ shader rất nhiều, nên các GPU thường có đơn vị xử lí shader chạy song song.

Nói về song song, có 3 cơ chế chính: MPI, OpenMP Stream Processing. Chức năng của GPU phù hợp với mô hình stream processing qua đó có 3 giai đoạn chính: gather, operate, và scatter. Nghĩa là dữ liệu (ví dụ: các pixels từ file hình ảnh) – có thể liên tục hoặc ở vị trí bất kì – sẽ được tập hợp lại (gather) thành 1 dòng (stream). Sau đó các một chuỗi các chương trình con (gọi là kernel functions) sẽ tác động tuần tự (operate) lên mỗi thành phần trong stream – thông thường mỗi kernel đều xử lí toàn bộ các thành phần trong stream (nên còn gọi là uniform streaming) và đầu ra của kernel function này sẽ là đầu vào của kernel function kế tiếp (nó hoạt động theo cơ chế pipeline) để giảm thiểu việc đọc/ghi dữ liệu trở lại bộ nhớ. Cuối cùng, dữ liệu đã xử lí được bởi mọi kernel functions sẽ được phân bố trở lại (scatter) bộ nhớ.

Streaming processing là cơ sở cho kĩ thuật graphics pipeline (rendering pipeline) cho phép chuyển đổi và biểu diễn từ hình ảnh dạng vectors ra hình ảnh dạng raster (pixels, dots) để có thể hiển thị/xuất ra ở các thiết bị đầu ra (màn hình, máy in, file ảnh lưu trong máy) với tốc độ cực kì nhanh (in realtime). Cùng với sự thay đổi của kiến trúc như đã đề cập ở trên, công nghệ VLSI cũng nâng tốc độ tính toán lên rất nhiều bằng cách tăng số lượng transistors trên một đơn vị diện tích.

Công nghệ chế tạo GPU

Công nghệ chế tạo GPU và số lượng transitor trên mỗi chip

GPGPU architectures?

Với sức mạnh của GPU ngày càng lấn át CPU, nhu cầu sử dụng GPU cho tính toán đa dụng (general purpose computation, cụ thể là cho các ứng dụng SIMD [single instruction, multiple data] hay vector-based datasets), không đơn thuần để tăng tốc cho các ứng dụng đồ họa (ASAP [application-specific application processor]), trở thành một vấn đề thiết thực. Từ đó khái niệm “general purpose GPU” ra đời.

Để so sánh giữa GPU vs. CPU, John Peddie, chủ tịch của John Peddie Research đã phát biểu: “The CPU cannot do the parallel operations that a GPU does, and a GPU cannot do the scalar operations that a CPU does. They need each other, and neither can replace the other.”

CPU-GPU-benchmarkGPGPU với hàng trăm cores có khả năng chạy hàng ngàn threads song song là một ứng viên tiềm tàng cho việc giải quyết các bài toán lớn có tính song song cao (real-time simulations, molecular simulations). Điều này đòi hỏi việc thêm một số lệnh máy cần thiết vào GPU. Đồng thời, để hỗ trợ việc sử dụng thuận lợi các lệnh cấp thấp của GPGPU, NVIDIA đã cung cấp một bộ thư viện mở rộng cho ngôn ngữ C, với tên gọi CUDA; và kiến trúc của các GPGPU card này gọi là Tesla. AMD/ATI thì có Brook+ nhưng không được bàn đến ở đây. Chú ý là miền ứng dụng của GPGPU không phải là cho mọi bài toán, có một số bài toán mà GPGPU không đem lại độ lợi (gain) đáng kể.

Tesla architecture & CUDA programming model

Kiến trúc logic

Ở phần này, có nhiều thuật ngữ quan trọng cần nắm. Trong CUDA programming model, CPU được xem là host, còn GPGPU được xem là device. Có thể có nhiều hosts và nhiều devices trên một máy tính. Cả hai đều có bộ nhớ riêng với tên gọi tương ứng là host memorydevice memory, i.e. không có bộ nhớ dùng chung. Vì thế, việc trao đổi dữ liệu cần copy qua lại và chỉ diễn ra khi không có đoạn code nào đang chạy trên GPGPU (để đảm biều điều này CUDA cung cấp 1 lệnh cho phép đồng bộ để đợi mọi lệnh trên GPU xong). MỘt số thử nghiệm cho thấy copy từ GPU->CPU nhanh hơn 1 ít so với CPU->GPU.

Với các đặc trưng của một GPU đã đề cập ở trên, GPGPU phù hợp để thực thi 1 tác vụ (i.e. subroutine/function) được gọi nhiều lần, mỗi lần áp dụng trên các dữ liệu đầu vào khác nhau. Đoạn code cho tác vụ như vậy khi thực thi trên GPGPU được gọi là 1 [device] kernel [function]. Khi có lệnh gọi kernel đó, chương trình sẽ kiểm tra (thông qua 1 cú pháp gọi kernel được qui định sẵn) xem có bao nhiêu hiện thực (instances, array of parallel threads)  chạy song song cho kernel đó. Điểm hạn chế hiện nay (cho tới thế hệ 2 của Tesla) là tại mỗi thời điểm chỉ có 1 kernel được thực thi, với số lượng hiện thực của kernel đó là rất lớn. Để dễ quản lí một số lượng lớn threads như vậy, cũng đồng thời giúp ánh xạ (mapping) dễ dàng từ dữ liệu – vector-based dataset – sang các threads, kiến trúc thread blocksgrids được sử dụng. Khi gọi 1 kernel thực thi, ta phải qui định tường minh dimension của block và của grid.

  • Để dễ quản lí, phân chia dữ liệu, các thread được gộp thành từng nhóm gọi là thread blocks theo kích thước 1-, 2- hoặc 3-D, và số lượng tối đa threads trong mỗi thread blocks là có giới hạn (<= 2^9 = 512 threads/block). Ví dụ: một 3D block thì có thể có kích thước là 8x8x8.
  • Để tăng số lượng thread song song có thể thực hiện cho 1 kernel, các thread blocks có thể gộp thành 1 grid [of thread blocks] với cách sắp xếp cũng có thể theo 1-, 2- hoặc 3-D (<= 2^32 blocks/grid). Như vậy, tối đa, ta có thể có 2^32 * 2^9 = 2^41 threads tổng cộng chạy song song cho 1 kernel.

Các thread của GPU là light-weight. Mỗi thread đều có dữ liệu của riêng nó (bao gồm thanh ghi – register – và local memory). Các threads trong cùng 1 block thì có thể chia sẽ dữ liệu qua shared memory, nhưng điều này không thể xảy ra với hai threads ở hai blocks khác nhau, ngoại trừ việc lưu trữ ở device memory (không nằm trên mỗi GPU chip – global memory). Tốc độ truy xuất đến dữ liệu trong shared memory là cực kì nhanh. Dữ liệu của thread lưu ở shared memory sẽ mất khi thread kết thúc. Nếu với dữ liệu quá lớn ( > 16KB), nó sẽ tự động được lưu trữ ở device memory chính là DDRAM (kích thước có thể đến 4GB, hoặc thậm chí 16GB với Tesla S1070). Dữ liệu lưu ở device memory sẽ tồn tại  trong suốt thời gian chương trình hoạt động (hoặc khi mà nó bị deallocate đối với dữ liệu động). Vì thế, dữ liệu lưu ở device memory có thể được truy xuất từ mọi kernel. Đồng thời, dữ liệu ở device memory của chương trình đang chạy sẽ là nơi trao đổi qua lới với host memory.

A grid with 1D array of threads

A grid with 1D array of thread blocks.

Grids_and_Blocks_of_Threads

A grid with 2D array of thread blocks, each block is a 2D array of threads.

Kiến trúc phần cứng

Thông tin trên là về mặt cấu trúc logic. Về mặt vật lí (phần cứng), Tesla GPGPU có các 32-bit cores (thread processors, streaming processor core, hay scalar processor- SP) – đơn vị xử lí nhỏ nhất. Số lượng là 128 cores với 1st gen. (generation), 240 cores với 2nd gen., và tương lai 512 cores với 3rd gen.

1st gen. chưa có bộ xử lí kép. Từ 2nd gen., Tesla có thêm 64-bit double precision processor (DP) nhưng số lượng bằng 1/8 so với SP. Một SP là đơn vị phần cứng để chạy tương ứng 1 thread,  i.e. mỗi SP chỉ xử lí 1 thread tại một thơi điểm mỗi SP hỗ trợ 128 threads. Cứ mỗi 8 SP, cùng với các đơn vị phần cứng khác (1 I-cache, 1 MT-issue, 1 C-cache, 2 SFU, 1 shared memory) được gộp thành một nhóm, tương ứng là 1 multiprocessors (streaming multiprocessor – SM). Rồi cứ mỗi 2 SM (hoặc nhiều hơn tuỳ vào kiến trúc), cùng với các đơn vị phần cứng khác (1 Texture unit, 1 Geometry controller (aka TPC controller), 1 SMC), tạo thành 1 group, tương ứng là 1 Thread Processing Clusters (TPC).

CUDA_SP

Mỗi 8 SP được gồm thành 1 SM. Tùy theo số lượng SP (32, 128, 240, ...) mà ta có số lượng SM khác nhau. Ở đây là kiến trúc cho GPU có 32 SP - 1st gen. Tesla.

Nói tóm lại, với 2nd gen. Tesla:

  1. texture unit gồm 24KB L1 cache (mỗi 2 SM có 1 texture unit) + ROP unit phục vụ cho các tác vụ bên đồ họa
  2. mỗi SM có
    tám 32-bit cores: có thể thực hiện 2 phép toán dấu chấm động (gồm 1 đơn vị để tính cọng + 1 đơn vị để tính nhân) trong 1 chu kì
    một 64-bit double-precision processsor (DP) có thể tính cộng/nhân với độ chính xác kép
    hai SFU cho phép tính các phép toán đặc biệt: nghịch đảo (1/x), 1/sqrt(x), sine, cosine, …
    8KB constant cache (lưu trữ các giá trị hằng số)
    instruction cache (lưu các lệnh của kernel)
    16KB shared memory + 64KB register memory
  3. mỗi special function unit (SFU) có thể thực hiện 4 phép toán dấu chấm động (4 đơn vị tính nhân) trong 1 chu kì
  4. nếu có 240 SP, và 128 threads chạy song song trong mỗi SP, thì ta có thể có tối đa 30,720 threads
  5. tỉ lệ số lượng DP trên SP là 1/8.

Về lí thuyết, nếu mọi units đều được sử dụng, mỗi SM có thể tính toán 8*2 + 2*4 = 24 tác vụ trong 1 chu kì. Hình vẽ sau cho thấy việc ánh xạ giữa phần cứng và phần mềm.

Mô hình hiện thực

Mô hình hiện thực

Khi gọi 1 kernel, nhà lập trình chỉ quan tâm là cần bao nhiêu thread blocks, và bao nhiêu thread trong mỗi block dùng để chạy kernel đó, còn việc ánh xạ thread blocks tới multiprocessor nào là việc của phần cứng, nhà lập trình không cần quan tâm. Đặc trưng này gọi là scalable programming model.

Ví dụ: NVIDIA GeForce 8 series có 128 SPs, được chia làm 16 SM. 16 SM lại được chia làm 8 đơn vị xử lí độc lập, gọi là Texture Processor Clusters (TPC). Mỗi multiprocessor có 8 single-precision streaming processors, 2 special-function units (SFU), 1 đơn vị lấy lệnh chạy đa luồng (multithreaded instruction fetch  and issue unit (MT issue)), 1 bộ nhớ đệm lưu các lệnh nạp sẵn (instruction cache), 1 bộ nhớ lưu trữ các biến hằng (read-only constant cache),  và 1 thanh ghi chia sẻ với dữ liệu tối đa 16-KB (read/write shared memory). GeForce 8 series

TPC_GeForce8

GeForce 8800 và một TPC gồm 2 SM

Tesla T8/G80 này gồm có 8 TPC. Mỗi TPC gồm có một texture unit và 2 SMs

TPC_Cuda

Ví dụ: NVIDIA GeForce 200 series có 240 SP, được chia làm 30 SM. Mỗi SM có 8 single-precision SP, 1 double-precision unit, và 1 shared memory. Như vậy, nếu dùng độ chính xác kép, mỗi SM tại mỗi thời điểm chỉ chạy 1 thread, thay vì là 8 đối với độ chính xác đơn. Do đó, tốc độ sẽ tăng 8 lần nếu dùng độ chính xác đơn. Các vấn đề khi sử dụng độ chính xác đơn hoặc kép sẽ được nói rõ ở các phần tiếp.

G80_Hardware

Tesla C1060 có 240 SP (freq: 1.3GHz, RAM:4GB, bus: PCI Expressx16 Gen.2), gồm 10 TPC (mỗi TPC có 24 SP, được gộp vào 3 SM, mỗi SM có 8 SP)

CUDA_Tesla_T10

Tesla T10 (2nd gen. Tesla)

Tesla S1070: gồm 4 Tesla T10 kết nối với nhau

Phân cấp bộ nhớ

Phân cấp bộ nhớ

Vì tốc độ chậm trong truy xuất bộ nhớ là nguyên nhân chủ yếu gây ra hiện tưỡng nghẽn cổ chai (bottleneck) trong các chương trình song song. Tesla tạo ra nhiều lớp (layer) bộ nhớ khác nhau với tốc độ truy cập khác nhau phù hợp cho các nhu cầu cụ thể. Quyền truy cập đến mỗi loại bộ nhớ cũng tuỳ vào cấp độ phần cứng sử dụng.

Các loại bộ nhớ cần biết:

  1. register (thanh ghi): (read/write) per-thread –> Mỗi SM có 8,192 registers.
  2. local memory: (read/write) per-thread  –>
  3. shared memory (read/write) per block
  4. global memory (read/write) per grid
  5. constant memory (read-only) per grid –> Mỗi SM có 8KB cho cả constant và texture
  6. texture memory (read-only) per grid

CUDA cung cấp các hàm cho phép cấp phát bộ nhớ động trên từng loại bộ nhớ cụ thể (ta sẽ tìm hiểu ở phần khác).memory_CUDA

Cơ chế phân công công việc

Cơ chế mà mỗi SM thực hiện là SIMT (single instruction multiple-threads), tương tự với cơ chế SIMD của CPU nhưng phức tạp hơn. Chi tiết cụ thể về kĩ thuật, tuy nhiên, không được công bố.

Trước hết, ta bàn về thuật ngữ thread trong CUDA. Đó đơn giản là phần tử cơ bản của dữ liệu cần xử lí, không có liên quan đến CPU threads hay GPU threads thuần túy. Thuật ngữ “warp” dùng để chỉ một nhóm 32 threads như vậy. 32 là kích thước tối thiểu để xử lí song song bởi 1 SM theo kiểu SIMD. Nói bên lề, thuật ngữ “warp” xuất phát từ “weaving”.

Để thuận lợi cho nhà lập trình, thay vì tương tác trưc tiếp với các warps, CUDA đưa ra khái niệm block, mỗi block chứa từ 64 đến 512 threads. Mỗi SM cho phép tối đa 8 blocks, tùy vào kích thước mỗi blocks miễn sao tổng số threads phải nhỏ hơn 768 (sẽ nói ở sau vì sao là 768). Vì thế, để tối ưu hóa công việc, nên phân chia số threads trong mỗi blocks là chia hết cho 32 (bạn có thể chia thành 1×32, hoặc 16×16, hoặc 8x8x16 … – kích thước mỗi block). Tiếp đến, việc nhóm các blocks vào trong 1 grid cho phép gọi và thực thi hàng ngàn threads chỉ bằng 1 lệnh gọi đơn, và không cần quan tâm đến tài nguyên của phần cứng. Nghĩa là nếu có ít SM, thì các blocks sẽ được thực thi tuần tự, còn nếu có nhiều SM thì chúng sẽ được thực thi song song. Đây chính là yếu tố làm nên đặc tính “extensible” của CUDA proramming model.

Hiện nay, 2nd gen. Tesla có SM gồm 8 SP và 1 DP. Mỗi SM có thể quản lí cùng lúc tới 24 warps, do đó cho phép tối đa 24×32=768 threads song song trên 1 SM. Như vậy, số threads trên toàn bộ GPGPU có thể lên tới

  • 16×768=12,288 threads (cho 1st gen., với 16 SM)
  • 30×768=23,040 thread (cho 2nd gen. với 30 SM)

Các threads thuộc cùng 1 warp thì bắt đầu cùng lúc, cùng thực hiện một lệnh, tuy nhiên chúng có thể hoạt động độc lập, i.e. rẽ nhánh (branching) có thể xảy ra. Nếu branching xảy ra, ví dụ theo 2 hướng A và B, thì một số threads sẽ thực hiện A trước, và các threads còn lại thực hiện B sẽ đợi cho tới khi A xong sẽ thực hiện. Chính vì thế, chúng có thể được đồng bộ với nhau, ví dụ: đảm bảo là mọi threads đều đã đọc xong dữ liệu của nó trước khi cho phép mỗi thread thực hiện tiếp tác vụ, hay là các threads cần phải kết thúc trước khi trở lại chương trình gọi.

Dù các GPGPU đời mới vẫn cho phép tính toán với double-precision point, GPGPU mặc định thực hiện tính toán với single-precision point. Do đó, nếu muốn dùng độ chính xác kép, ta cần chỉ định lúc biên dịch với compiler flag–gpu-name sm_13” cho ngôn ngữ C (với Fortran sẽ đề cập sau). Cần nhắc lại là mỗi SM (ví dụ: gồm 8 SP) chỉ có 1 ALU cho tính toán độ chính xác kép. Vì thế, tốc độ lúc dùng double-precision point sẽ bị chậm lại (ví dụ: 8 lần).

SIMT_Cuda

1 dòng lệnh, nhưng chạy đa luồng

Khi lập trình, cần chỉ rõ grid và block dimensions (kích thước tối đa là tùy vào giới hạn phần cứng). Mỗi blockthread đều có một số định danh (ID). Để xác định duy nhất 1 thread, ta cần biết thread block ID, và 1 local thread ID.

Tối ưu phân chia công việc

Để chương trình chạy tốt, ta cần cân bằng giữa số blocks và số thread trong mỗi block khi gọi một kernel. Nếu số threads lớn, thì thì số registers available cho mỗi thread sẽ giảm đi, nghĩa là khả năng lưu dữ liệu truy xuất nhanh bị giảm lại. Mỗi SM có 8,192 registers; mỗi thanh ghi 16KB. Nếu dùng tối đa 768 threads, thì mỗi thread chỉ có thể có khoảng 10 registers, hay ~ 160KB cho bộ nhớ nhanh.

Chú ý là mỗi SM có tối đa 768 threads song song, nên nếu blocks gồm 512 threads thì chỉ có thể có 1 block active trên 1 SM. Như vậy sẽ phí đi 768-512=256 threads. NVIDIA khuyên nên dùng blocks có kích thước trong khoảng từ 128 (1×128, 8×16, 8x8x2, 4x4x8) đến 256 thread (1×256, 16×16, 8x8x16).

Đồng bộ hoạt động giữa các threads:

Các threads của một kernel chạy bất đồng bộ với nhau.  Để đảm bảo sự đồng bộ giữa chúng, ví dụ, khi cùng truy cập một dữ liệu dùng chung, ta dùng cudaThreadSynchronize().

Lúc đó, chương trình sẽ chỉ thực thi kernel kế tiếp khi mà mọi threads của kernel trước đã kết thúc.

Tổng kết

CUDA cung cấp:

  • mô hình về bộ nhớ
  • mô hình về lập trình

CUDA cho phép:

  • codes có thể chạy trên mọi qui mô (scalable or “extensible” parallel programming model), i.e. không cần biên dịch lại khi chạy trên máy có số processor khác. Cụ thể như sau, nếu ta cần chạy 1 kernel với grid gồm 8 blocks. Nếu GPU có 2 core, thì nó sẽ là song song từng 2 blocks, nếu nó có 4 cores, thì sẽ tự động song song từng 4 blocks

CUDA_scalable

  • hỗ trợ codes chạy trên môi trường host lẫn device.
  • mỗi thời điểm chỉ 1 kernel được thực thi, tuy nhiên nhiều threads (arrays of threads) đồng thời thực thi kernel đó và trên vài trăm cores (thread processors), i.e. tương ứng vài ngàn threads song song.
  • các threads trong cùng 1 block có thể phối hợp với nhau (ví dụ: chia sẻ dữ liệu, đồng bồ về thực thi)
  • nhà lập trình chỉ định số lượng threads/blocks, còn phần cứng sẽ quản lí thread processors nào xử lí chúng
  • chính cơ chế thread blocks (với 1-, 2-, và 3-D), và grid of blocks (với 1-, 2-D) cho phép đơn giản hóa việc xử lí các dữ liệu 2 chiều và 3 chiếu (ví dụ: xử lí ảnh, tính toán phương trình vi phần từng phần (PDE ) …)
Mỗi kernel có thể được thực thi song song

Mỗi kernel có thể được thực thi song song

Sự khác biệt giữa GPU và CPU threads:

  • GPU thread là cực kì nhỏ gọn (lightweight)
  • Mỗi GPU có thể chạy hàng ngàn threads song song (multi-core CPU chỉ có thể chạy vài chục threads song song)
  • Threads tự động kết thúc sau khi xong

Kiến trúc tương lai

Có thể nói thêm đôi chút về viễn cảnh của tương lai khi mà CPU+GPU có thể được kết hợp lại (CPU/GPU fusion). Một hướng khác là sự tích hợp giữa CPU core và accelerator core, gọi là APU – Accelerated Processing Unit, theo AMD. Nó có thể là một sự kết hợp giữa CPU cores (scalar processing cores), GPU cores (parallel processing cores), và fixed-function accelerator cores. Intel thì đang phát triển kiến trúc mới gọi là Larrabee còn IBM, Sony và Toshiba liên kết để tạo ra kiến trúc  CELL.

Tham khảo:

  1. https://computing.llnl.gov/tutorials/parallel_comp/
  2. http://www.anandtech.com/video/showdoc.aspx?i=3651
  3. http://en.wikipedia.org/wiki/Comparison_of_MPI,_OpenMP,_and_Stream_Processing
  4. http://www.cs.utexas.edu/~skeckler/wild04/Paper14.pdf (Stream Processing in General-Purpose Processors)
  5. http://saahpc.ncsa.illinois.edu/papers/Chai_paper.pdf
  6. https://visualization.hpc.mil/wiki/GPGPU
  7. http://www.cs.tut.fi/~pk/seminaarit/gpu-2009/cuda-esitys.pdf
  8. http://www.sdsc.edu/us/training/assets/docs/NVIDIA-02-BasicsOfCUDA.pdf
  9. http://www.computerpoweruser.com/editorial/article.asp?article=articles/archive/c0807/30c07/30c07.asp&guid=
  10. http://www.tomshardware.com/reviews/nvidia-cuda-gpu,1954-7.html (warp)
  11. Larrabee: A Many-Core x86 Architecture for Visual Computing
  12. http://en.wikipedia.org/wiki/Larrabee_(GPU)
  13. http://en.wikipedia.org/wiki/CELL

Written by vietnamen

Tháng Mười 3, 2009 lúc 11:06 chiều

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s

%d bloggers like this: