I/O, Lưu trữ & Tài nguyên/GPU về mặt kiến trúc — SIMT, băng thông, throughput
16/26
Bài 16 / 26~14 phútĐường đi I/O & GPUMiễn phí lượt xem

GPU về mặt kiến trúc — SIMT, băng thông, throughput

Vì sao GPU có hàng nghìn nhân nhưng mỗi nhân chậm hơn CPU: SIMT khác SIMD ra sao, băng thông bộ nhớ, và loại bài toán nào hợp với kiến trúc đánh đổi đó.

TL;DR: GPU không nhanh hơn CPU, nó đánh đổi khác. CPU tiêu phần lớn diện tích chip để một luồng lệnh chạy nhanh nhất: cache lớn, dự đoán rẽ nhánh, thực thi ngoài thứ tự. GPU bỏ gần hết để lấy chỗ nhét thêm đơn vị tính toán, rồi giấu độ trễ bằng cách luôn có nhóm thread khác sẵn sàng chạy khi nhóm hiện tại phải chờ bộ nhớ. Mô hình thực thi gọi là SIMT: thread được gom thành warp 32 thread cùng chạy một lệnh. Hệ quả hay bị hiểu sai nhất: khi các thread trong một warp rẽ hai nhánh khác nhau, GPU tuần tự hoá hai nhánh đó.

Một GPU tầm trung quảng cáo vài nghìn nhân, còn CPU của bạn có tám. Vậy nhưng chạy một vòng lặp tuần tự bình thường trên GPU thì chậm hơn CPU nhiều lần.

Nghịch lý đó tan biến khi bạn nhìn kiến trúc thay vì nhìn con số nhân. Bài này giải thích GPU đổi gì lấy gì, và vì sao loại bài toán bạn giao cho nó quyết định gần như toàn bộ kết quả.

1. Analogy — một đầu bếp giỏi và một trăm phụ bếp

Một nhà hàng có đầu bếp trưởng cực giỏi: món nào cũng nấu được, xử lý đơn phức tạp, tự quyết mọi tình huống. Đó là CPU. Giao cho ông ấy một nghìn đơn giống hệt nhau, ông vẫn phải làm lần lượt từng đơn.

Bên cạnh là một dãy một trăm phụ bếp. Mỗi người kém hơn hẳn, và có một điều kiện ngặt nghèo: cả nhóm phải làm cùng một thao tác tại cùng thời điểm — quản lý hô "thái hành" thì tất cả cùng thái hành. Một nghìn đơn giống nhau thì dãy này nghiền xong trong chớp mắt. Nhưng một đơn đòi món riêng thì cả trăm người đứng nhìn một người làm.

Trong bếpTrên phần cứng
Đầu bếp trưởngLõi CPU — mạnh, linh hoạt, độ trễ thấp
Một dãy phụ bếpCác nhân GPU trong một warp
Quản lý hô một thao tác cho cả dãyMột bộ đếm chương trình dùng chung cho warp
Đơn đòi món riêng, cả dãy đứng nhìnWarp divergence
Nhóm này chờ nguyên liệu thì gọi nhóm khác vàoĐổi warp để giấu độ trễ bộ nhớ

2. SIMT là gì, và khác SIMD ở chỗ nào?

Tài liệu profiling của NVIDIA phát biểu gọn: các khối thread được chia thành nhóm 32 thread gọi là warp, và bộ đa xử lý thực thi theo mô hình SIMT (Single Instruction, Multiple Threads), cho phép từng thread có luồng điều khiển riêng trong khi vẫn chạy như một phần của warp.

Câu đó gói cả hai nửa của vấn đề. Nửa thứ nhất: mỗi thread trông như một luồng độc lập, bạn viết code cho một thread rồi phần cứng nhân nó lên hàng nghìn lần. Nửa thứ hai: chúng không thực sự độc lập, vì cả warp chia sẻ một lệnh tại mỗi thời điểm.

Đặt cạnh SIMD trên CPU sẽ rõ hơn. Với SIMD, bạn viết tường minh một lệnh tác động lên một vector — lập trình viên phải nghĩ theo vector, và độ rộng vector lộ ra trong chính tập lệnh. Với SIMT, bạn viết code theo góc nhìn một thread đơn lẻ, còn việc gom 32 thread vào một warp là chuyện của phần cứng.

Sự tiện lợi ấy có giá của nó, và cái giá nằm ở chỗ trừu tượng "mỗi thread độc lập" bị rò rỉ đúng lúc bạn viết một câu if.

3. Tự dự đoán — điều gì xảy ra khi một warp gặp if?

Hãy đọc đoạn giả mã sau rồi tự điền hai ô trước khi đọc tiếp. Warp 32 thread, 16 thread thoả điều kiện và 16 thread không.

if dieuKien(threadId):
    lamViecA()      -- ton 100 chu ky
else:
    lamViecB()      -- ton 100 chu ky

-- Tong chu ky ca warp di qua khoi if/else = /* TODO 1 */
-- So thread lam viec huu ich trung binh    = /* TODO 2 */
Tự điền trước khi xem đáp án

Câu hỏi định hướng: cả warp dùng chung một luồng lệnh. Khi hai nhóm thread cần hai nhánh khác nhau, phần cứng có chạy được cả hai cùng lúc không?

Đáp án. Ô 1: 200 chu kỳ. Ô 2: trung bình 16 thread (một nửa warp) làm việc hữu ích tại mỗi thời điểm.

Cơ chế: cả warp chỉ có một luồng lệnh đang chạy. Khi thread trong warp rẽ hai hướng khác nhau, phần cứng không chạy hai nhánh song song được — nó tuần tự hoá. Đây gọi là warp divergence.

Warp 32 thread gap mot cau if: hai luot noi tiep, moi luot mot nua warp bi vo hieu

Hệ quả là câu phải nhớ kỹ nhất của bài: song song trên GPU là song song giữa các warp, không phải bên trong một warp. Thuật toán nhiều rẽ nhánh phụ thuộc dữ liệu sẽ chạy tệ dù bạn có bao nhiêu nhân đi nữa. Ngược lại, nếu rẽ nhánh chia theo khối lớn sao cho cả warp cùng đi một hướng thì không mất gì — divergence chỉ tính trong phạm vi một warp.

4. Giấu độ trễ và băng thông — hai nửa còn lại

Nếu GPU bỏ hết cache lớn và cơ chế thực thi ngoài thứ tự, thì nó xử lý độ trễ bộ nhớ bằng cách nào? Câu trả lời là không xử lý — nó giấu đi.

CPU đối phó với độ trễ bằng cách cố làm một luồng chạy tiếp: cache nhiều tầng, dự đoán rẽ nhánh, thực thi ngoài thứ tự. Chiến lược đó tốn rất nhiều transistor cho phần điều khiển.

GPU đi đường ngược lại. Warp nào phải chờ bộ nhớ thì bộ lập lịch cất sang một bên và cho warp khác vào chạy ngay — chuyển đổi gần như không tốn gì vì trạng thái mọi warp đã nằm sẵn trong tập thanh ghi rất lớn. Chừng nào còn đủ warp sẵn sàng thì đơn vị tính toán không bao giờ rỗi, và độ trễ từng lần truy cập trở nên vô hình ở mức tổng thể.

Nói "GPU nhanh hơn CPU" vì thế là sai: GPU có độ trễ cho một phép tính cao hơn, thứ nó tối ưu là tổng số phép tính hoàn thành mỗi giây.

Nửa còn lại là bộ nhớ. Bộ nhớ của GPU cao cấp có băng thông cao hơn RAM của CPU vài lần, nhưng con số danh nghĩa đó chỉ đạt được khi truy cập gộp được. Tài liệu best practices của NVIDIA mô tả: các lệnh đọc ghi bộ nhớ toàn cục của các thread trong một warp được thiết bị gộp lại thành càng ít giao dịch càng tốt. Nếu 32 thread trong warp đọc 32 ô liền kề, phần cứng gom thành vài giao dịch lớn. Nếu chúng đọc 32 chỗ rải rác, con số giao dịch nhân lên và băng thông thực tụt sâu — cùng một thuật toán, cùng một GPU, chỉ khác cách sắp dữ liệu trong bộ nhớ.

Nhớ lại AoS so với SoA

Bài đó cho thấy cách bố trí dữ liệu quyết định hiệu năng trên CPU vì nó quyết định cache line có được dùng hết hay không. Trên GPU cùng một nguyên lý lại còn gắt hơn: bố trí sai thì truy cập không gộp được, và bạn mất phần lớn băng thông danh nghĩa.

5. Pitfall của riêng concept này

Nhầm 1 — nghĩ GPU chạy song song cả hai nhánh của một câu if.

✅ Thread cùng warp rẽ hai hướng thì hai nhánh chạy tuần tự, mỗi lượt một phần warp ngồi không. Muốn tránh thì sắp xếp sao cho rẽ nhánh chia theo khối lớn, để cả warp cùng đi một đường.

Nhầm 2 — so sánh GPU với CPU bằng số nhân.

✅ Một nhân GPU không tương đương một lõi CPU: nó chậm hơn, không tự quyết luồng điều khiển riêng, và chỉ có nghĩa khi nằm trong một warp cùng làm một việc. So bằng số nhân cũng giống so hai đội bằng số người mà không hỏi họ làm được gì.

Nhầm 3 — port một thuật toán tuần tự sang GPU rồi kỳ vọng nhanh hơn.

✅ Thuật toán có phụ thuộc tuần tự giữa các bước không có gì để GPU khai thác, và chạy trên đó còn chậm hơn CPU vì mỗi nhân yếu hơn. GPU cần bài toán chia được thành hàng nghìn phần việc độc lập làm cùng một thao tác.

Nhầm 4 — bỏ qua cách sắp xếp dữ liệu trong bộ nhớ.

✅ Băng thông danh nghĩa chỉ đạt được khi truy cập của một warp gộp được thành ít giao dịch. Cùng thuật toán mà đổi bố trí dữ liệu có thể đổi hẳn kết quả, đúng như bài học AoS so với SoA trên CPU nhưng gắt hơn.

6. 📚 Đào sâu (tuỳ chọn)

📚 Nguồn kỹ thuật
  • Nsight Compute — Kernel Profiling Guide — nguồn chính chủ cho định nghĩa warp 32 thread và mô hình thực thi SIMT, kèm các chỉ số dùng để đo hiệu suất kernel trong thực tế.
  • CUDA C++ Best Practices Guide — mục về truy cập bộ nhớ toàn cục giải thích việc gộp giao dịch của một warp, và vì sao bố trí dữ liệu quyết định băng thông thực đạt được.

7. Liên hệ các bài khác

  • Polling, interrupt và DMA — GPU dùng đúng ba cơ chế đó để nói chuyện với CPU, chỉ ở quy mô lớn hơn nhiều.
  • AoS so với SoA — cùng bài học bố trí dữ liệu, phiên bản CPU; trên GPU nó quyết định truy cập có gộp được hay không.
  • Out-of-order và SIMD — những cơ chế mà CPU dùng để giấu độ trễ, và cũng chính là những thứ GPU cố ý bỏ đi.
  • Host và device — bài kế tiếp: kiến trúc mạnh thế này rồi vẫn có thể nghẽn ở đoạn dây nối nó với CPU.

8. Tóm tắt

  • GPU thắng khi bài toán tách được thành hàng nghìn việc độc lập cùng làm một thao tác; nhét một thuật toán tuần tự phụ thuộc bước trước vào đó thì còn chậm hơn CPU.
  • SIMT gom thread thành warp 32 thread dùng chung một luồng lệnh; ảo giác "mỗi thread độc lập" rò rỉ ngay tại câu if đầu tiên.
  • Muốn né warp divergence thì gom rẽ nhánh theo khối lớn để cả warp cùng đi một hướng, đừng để nó phụ thuộc dữ liệu của từng thread.
  • Chiến lược giấu độ trễ chỉ hoạt động khi luôn còn warp sẵn sàng, tức là khi bạn cấp đủ việc song song.
  • Băng thông danh nghĩa chỉ đạt được khi truy cập của một warp gộp được thành ít giao dịch, nên bố trí dữ liệu là quyết định kiến trúc chứ không phải chi tiết vặt.

9. Tự kiểm tra

Tự kiểm tra
Q1
Một warp 32 thread chạy khối if/else, trong đó 16 thread đi nhánh A và 16 thread đi nhánh B, mỗi nhánh tốn 100 chu kỳ. Cả warp mất bao lâu, và vì sao?

200 chu kỳ, vì phần cứng tuần tự hoá hai nhánh: cả warp chỉ có một luồng lệnh tại mỗi thời điểm, nên GPU chạy nhánh A với 16 thread hoạt động và 16 thread bị vô hiệu hoá, rồi chạy nhánh B và đảo vai — trung bình chỉ một nửa số thread làm việc hữu ích. Kết luận rút ra là song song trên GPU nằm giữa các warp chứ không nằm trong một warp; nếu rẽ nhánh chia theo khối lớn sao cho cả warp cùng đi một hướng thì không mất gì.

Q2
SIMT khác SIMD ở chỗ nào? Sự khác biệt đó khiến việc lập trình dễ hơn ra sao, và cái giá phải trả là gì?

Với SIMD, lập trình viên viết tường minh một lệnh tác động lên cả một vector, nên phải nghĩ theo vector và độ rộng vector lộ ra trong tập lệnh. Với SIMT, bạn viết code theo góc nhìn một thread đơn lẻ, còn việc gom 32 thread thành một warp là chuyện của phần cứng.

Cái giá là một trừu tượng bị rò rỉ. Code trông như thể mỗi thread độc lập, nhưng chúng chia chung một luồng lệnh, nên ngay khi bạn viết một câu if phụ thuộc dữ liệu thì hiệu năng sụt mà không có gì trong cú pháp cảnh báo bạn.

Q3
Vì sao nói GPU 'giấu' độ trễ bộ nhớ thay vì 'giảm' nó? Chiến lược đó cần điều kiện gì mới hoạt động?

Vì độ trễ mỗi lần truy cập không hề nhỏ đi. GPU chỉ đơn giản cất warp đang chờ sang một bên và cho warp khác vào chạy ngay, và việc chuyển gần như không tốn gì vì trạng thái mọi warp đã nằm sẵn trong tập thanh ghi rất lớn. Các đơn vị tính toán vì thế không rỗi, dù từng phép truy cập vẫn chậm.

Điều kiện là phải luôn còn warp sẵn sàng để thay. Nếu bài toán không đủ song song, hoặc mỗi thread ngốn quá nhiều thanh ghi nên số warp cùng trú ngụ bị giảm, thì không còn ai để thay và độ trễ lộ nguyên hình.

Q4
Hai phiên bản của cùng một kernel chạy trên cùng một GPU, cùng số phép tính, nhưng một bản nhanh hơn nhiều lần. Khác biệt nằm ở cách sắp xếp dữ liệu. Cơ chế nào giải thích điều đó?

Gộp truy cập bộ nhớ. Tài liệu best practices mô tả rằng các lệnh đọc ghi bộ nhớ toàn cục của các thread trong một warp được thiết bị gộp thành càng ít giao dịch càng tốt. Nếu 32 thread đọc 32 ô liền kề, phần cứng gom thành vài giao dịch lớn.

Nếu chúng đọc 32 chỗ rải rác thì số giao dịch nhân lên và băng thông thực tụt sâu, dù thông số danh nghĩa của GPU không đổi. Đây đúng là bài học AoS so với SoA đã gặp ở tầng cache của CPU, nhưng trên GPU thì hậu quả nặng hơn.

Q5
Đồng nghiệp muốn port một thuật toán mô phỏng có phụ thuộc tuần tự giữa các bước sang GPU vì 'GPU có 5000 nhân còn server chỉ có 32 lõi'. Lập luận sai ở đâu?

Sai ở chỗ coi một nhân GPU tương đương một lõi CPU. Nhân GPU chậm hơn hẳn, không tự quyết luồng điều khiển riêng, và chỉ có ý nghĩa khi nằm trong một warp cùng làm một thao tác. Đếm nhân để so hai kiến trúc cũng như so hai đội bằng số người mà không hỏi họ làm được gì.

Nặng hơn là bản chất bài toán: có phụ thuộc tuần tự giữa các bước thì không có gì để chia cho hàng nghìn thread cùng làm. Chạy trên GPU nhiều khả năng còn chậm hơn CPU. GPU cần bài toán tách được thành hàng nghìn phần việc độc lập thực hiện cùng một thao tác.

Bài tiếp theo: Host và device — vì sao chép dữ liệu đắt hơn tính

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Host và device — vì sao chép dữ liệu đắt hơn tính