Như đã thấy ở Phần 3, lịch sử máy tính đã chứng kiến một bước ngoặt vào những năm 2010: Sự sụp đổ của Định luật Moore về mặt tốc độ. Khi xung nhịp chạm ngưỡng 3.5 GHz và bài toán nhiệt năng trở thành ‘bức tường’ ngăn cản sự phát triển theo chiều dọc, thế giới công nghệ buộc phải chuyển mình sang chiều ngang.
Khi tốc độ đơn nhân không còn tăng, tính toán song song (Parallel Computing) không còn là một lựa chọn xa xỉ, mà là con đường duy nhất để duy trì đà tăng trưởng của công nghệ.
![[A.I System Programming] B4: Motivation của GPU 10 image 82 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-82-1024x424.png)
Tính toán song song
Tính toán song song là việc thực thi nhiều thao tác tại một thời điểm duy nhất. Như đã thấy từ biểu đồ trên, Single Thread Performance (Hiệu năng đơn nhân), Frequency (Xung nhịp) của CPU đã không còn tăng đáng kể từ sau những năm 2010, thậm chí là giảm xuống/giữ nguyên để đạt được mục tiêu tiết kiệm điện hơn, nhiệt năng giảm đi. Sự thay đổi lớn nhất từ 2010 là số lượng nhân (Logical Core)
Tuy nhiên, việc các hệ thống máy tính được trang bị nhiều core hơn, mở ra khả nưang cho phép thực thi nhiều thao tác tại 1 thời điểm duy nhất, nhưng việc này không diễn ra tự động. Tức là từ góc nhìn của 1 lập trình viên, không phải cứ đưa chương trình vào các phần cứng nhiều nhân là chương trình sẽ chạy nhanh hơn, mà điều này còn cần chính lập trình viên đó hiểu và sửa đổi chương trình để có thể tận dụng khả năng của phần cứng.
- Nhìn chung đầu tiên, ta cần xác định liệu ứng dụng có tiềm năng/có khả năng tận dụng từ tính toán song song không?
- Tiềm năng song song, hay tính đồng thời (concurrency), có nghĩa là ta có thể xác nhận rằng việc thực hiện các thao tác theo bất kỳ thứ tự nào là an toàn khi các tài nguyên hệ thống trở nên sẵn dụng.
Và, với tính toán song song, có một yêu cầu bổ sung: các thao tác này phải xảy ra cùng một lúc. Để điều này thực hiện được, ta cũng phải tận dụng đúng cách các tài nguyên để thực thi các thao tác này một cách đồng thời.
Nghe có vẻ khá hàn lâm, ta có thể đi qua 1 ví dụ thực tế
Ví dụ thực tế
![[A.I System Programming] B4: Motivation của GPU 11 image 83 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-83-1024x594.png)
Có thể thấy vô số ví dụ về xử lý song song ngay trong cuộc sống hàng ngày. Ví dụ là một hàng thanh toán tại siêu thị, mục tiêu là giúp khách hàng thanh toán nhanh chóng những món đồ họ muốn mua.
Việc này có thể được thực hiện bằng cách thuê nhiều thu ngân để xử lý (thanh toán) cho khách hàng từng người một. Trong trường hợp này, những thu ngân lành nghề có thể thực hiện quy trình thanh toán nhanh hơn để khách hàng rời đi sớm hơn.
Một chiến lược khác là sử dụng nhiều trạm tự thanh toán và cho phép khách hàng tự thực hiện quy trình. Chiến lược này đòi hỏi ít nguồn lực con người hơn từ siêu thị và có thể mở thêm nhiều làn để xử lý khách hàng. Quầy tự thanh toán thì có thể sẽ xử lý chậm hơn quầy thu ngân do khách hàng không quen với việc này, tuy nhiên tổng thể thì nhiều khách hàng hơn có thể thanh toán nhanh chóng nhờ tính song song được tăng cường, dẫn đến các hàng đợi ngắn hơn.
Chúng ta giải quyết các vấn đề tính toán bằng cách phát triển các thuật toán (algorithms): một tập hợp các bước để đạt được kết quả mong muốn.
- Trong phép ẩn dụ về siêu thị, quy trình thanh toán chính là thuật toán.
- Trong trường hợp này, nó bao gồm việc:
- B1: dỡ đồ khỏi giỏ
- B2: quét mã các món đồ để lấy giá
- B3: thanh toán tiền.
- Thuật toán này là tuần tự (sequential/serial); nó phải tuân theo đúng thứ tự này. Rõ ràng ta không thể vừa bảo khách hàng hãy thanh toán tiền, nếu ta chưa dỡ đồ ra để quét mã kiểm tra giá.
Nếu có hàng trăm khách hàng cần thực hiện nhiệm vụ này, thuật toán thanh toán cho nhiều khách hàng sẽ chứa đựng một tính song song có thể tận dụng được. Về mặt lý thuyết, không có sự phụ thuộc nào giữa bất kỳ hai khách hàng nào đang thực hiện quy trình thanh toán. Bằng cách sử dụng nhiều làn thanh toán hoặc các trạm tự thanh toán, các siêu thị đã bộc lộ tính song song (expose parallelism), từ đó tăng tốc độ khách hàng mua hàng và rời khỏi cửa hàng. Mỗi lựa chọn trong cách chúng ta triển khai tính song song này đều dẫn đến các chi phí và lợi ích khác nhau.
Định nghĩa: Tính toán song song (Parallel computing) là việc thực hành xác định và bộc lộ tính song song trong các thuật toán, thể hiện điều này trong phần mềm của chúng ta, và hiểu rõ các chi phí, lợi ích cũng như những giới hạn của cách triển khai đã chọn.
Tính năng phần cứng
Vì có rất nhiều tính năng phần cứng song song khác nhau, điều này đặt ra những thách thức mới cho lập trình viên. Một trong số đó là siêu phân luồng (hyperthreading), được giới thiệu bởi Intel. Việc có hai hàng đợi lệnh xen kẽ công việc vào các đơn vị logic phần cứng cho phép một lõi vật lý hiển thị như hai lõi đối với hệ điều hành (OS).
Bộ xử lý vector (Vector processors) là một tính năng phần cứng khác bắt đầu xuất hiện trong các bộ vi xử lý phổ thông vào khoảng năm 2000. Chúng thực thi nhiều lệnh cùng một lúc. Độ rộng tính bằng bit của bộ xử lý vector (còn gọi là đơn vị vector – vector unit) xác định số lượng lệnh thực thi đồng thời. Ví dụ, một đơn vị vector rộng 256-bit có thể thực thi bốn lệnh 64-bit (số thực dấu phẩy động độ chính xác kép – doubles) hoặc tám lệnh 32-bit (độ chính xác đơn – single-precision) cùng một lúc.
Có thể tạm bỏ qua cách mà các công nghệ này hoạt động, từ một phép tính, ta thử xem xét một CPU 16 lõi có siêu phân luồng và một đơn vị vector rộng 256-bit. Một chương trình tuần tự (serial) chỉ sử dụng một lõi duy nhất và không sử dụng vector hóa sẽ chỉ tận dụng được 0,8% khả năng xử lý lý thuyết của bộ vi xử lý này!
16 cores × 2 hyperthreads × (256 bit-wide vector unit)/(64-bit double) = 128-way parallelism
![[A.I System Programming] B4: Motivation của GPU 12 image 84 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-84.png)
Hiệu quả năng lượng: Làm được nhiều hơn với ít tài nguyên hơn
Một trong những lĩnh vực tác động mới của tính toán song song là hiệu quả năng lượng. Trên các thiết bị cầm tay, tính song song giúp ứng dụng chạy nhanh hơn, cho phép thiết bị quay lại chế độ ngủ (sleep mode) sớm hơn. Nó cũng cho phép sử dụng các bộ vi xử lý chậm hơn nhưng có tính song song cao hơn, vốn tiêu thụ ít điện năng hơn.
Công thức ước tính mức tiêu thụ năng lượng của ứng dụng:
Trong đó: P là năng lượng tiêu thụ, N là số bộ xử lý, R là công suất thiết kế nhiệt (TDP), và T là thời gian chạy.
Ví dụ:
- Dùng CPU: 20 bộ xử lý Xeon (120W) chạy trong 24 giờ tiêu tốn 57,60 kWh.
- Dùng GPU: 4 GPU NVIDIA V100 (300W) chạy trong 24 giờ tiêu tốn 28,80 kWh. Dù thời gian chạy bằng nhau, phiên bản dùng GPU chỉ tiêu tốn một nửa chi phí năng lượng.
Các định luật cơ bản của tính toán song song
Trong tính toán tuần tự, mọi thao tác đều nhanh lên khi tần số xung nhịp tăng. Ngược lại, với tính toán song song, chúng ta phải sửa đổi ứng dụng để khai thác phần cứng.
Định luật Amdahl
Định luật Amdahl (đề xuất bởi Gene Amdahl năm 1967) mô tả sự tăng tốc của một vấn đề có kích thước cố định khi số lượng bộ xử lý tăng lên:
Trong đó: P là phần mã có thể song song hóa, S là phần mã tuần tự (S + P = 1), và N là số lượng bộ xử lý.
Ví dụ một biểu đồ trục hoành là số lượng nhân và trục tung là tốc độ tăng lên. Trong mong muốn, khi ta đi từ 1 nhân lên 32 nhân, ta có thể tăng tốc độ lên đúng 32 lần. Tuy nhiên nếu chỉ cần với 10% mã không thể song song hoá, tức là có 90% mã song song hoá được thôi, thì speedup cũng đã chỉ còn là khoảng ~8 ở 32 nhân (so với 32 lần tăng tốc)
![[A.I System Programming] B4: Motivation của GPU 13 image 85 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-85.png)
Hãy tưởng tượng một quy trình làm giày gồm:
- Thiết kế mẫu (Tuần tự): 10 giờ (Chỉ có 1 nhà thiết kế chính có thể quyết định kiểu dáng).
- Gia công và đóng gói (Song song): 90 giờ.
Áp dụng công thức: P = 0.9 (90%), S = 0.1 (10%).
- Với 2 bộ xử lý (N=2): Speedup = 1.82 lần.
- Với 10 bộ xử lý (N=10): Speedup = 5.26 lần.
- Với vô hạn bộ xử lý: Speedup = 10 lần.
Dù có trang bị hệ thống máy móc khổng lồ (tăng N lên cực lớn), ta cũng không bao giờ tăng tốc quy trình sản xuất lên quá 10 lần. 10 giờ thiết kế ban đầu là “vùng bất khả xâm phạm”.
Định luật Gustafson-Barsis
Gustafson và Barsis (1988) chỉ ra rằng: trong thực tế, khi có thêm nhiều bộ xử lý, chúng ta thường tăng kích thước vấn đề lên. Nếu kích thước vấn đề tăng tỷ lệ thuận với số lượng bộ xử lý, sự tăng tốc được tính bằng:
Kết quả là một vấn đề lớn hơn có thể được giải quyết trong cùng một khoảng thời gian bằng cách sử dụng nhiều bộ xử lý hơn. Kịch bản này được gọi là mở rộng yếu (weak scaling).
![[A.I System Programming] B4: Motivation của GPU 14 image 86 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-86.png)
Định nghĩa: Weak scaling đại diện cho thời gian tìm ra lời giải so với số lượng bộ xử lý cho một kích thước vấn đề cố định trên mỗi bộ xử lý.
Giả sử ta có 1 giờ để dọn dẹp và ta có một số lượng nhân công tùy ý.
- Phần tuần tự (S): Lập kế hoạch phân công công việc (mất 5 phút). Dù có bao nhiêu người, vẫn phải mất 5 phút này để nói cho mọi người biết phải làm gì.
- Phần song song (P): Việc lau chùi thực tế.
Phân tích:
- Với 1 người: Trong 1 giờ, ta dành 5 phút lập kế hoạch và 55 phút còn lại để lau xong 1 căn phòng.
- Với 10 người: vẫn dành 5 phút lập kế hoạch. Nhưng trong 55 phút còn lại, 10 người này có thể lau sạch 10 căn phòng.
Kết luận: Ta không làm sạch 1 căn phòng nhanh gấp 10 lần (vì vẫn vướng 5 phút họp), nhưng ta đã giải quyết được một khối lượng công việc lớn gấp nhiều lần trong cùng một quỹ thời gian 1 giờ.
So sánh Mở rộng Mạnh (Strong Scaling) và Mở rộng Yếu (Weak Scaling)
- Mở rộng mạnh (Strong scaling): Giữ nguyên tổng kích thước bài toán và chia nhỏ nó ra cho nhiều bộ xử lý hơn. Mục tiêu chính là giảm thời gian tính toán.
- Mở rộng yếu (Weak scaling): Giữ nguyên kích thước bài toán trên mỗi bộ xử lý (mật độ không đổi), dẫn đến tổng kích thước bài toán tăng lên khi thêm bộ xử lý. Điều này giúp tận dụng thêm bộ nhớ của các bộ xử lý mới để giải quyết vấn đề lớn hơn.
![[A.I System Programming] B4: Motivation của GPU 15 image 87 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-87-1024x742.png)
Trong thực tế, cả hai đều quan trọng vì chúng giải quyết các kịch bản khác nhau. Thuật ngữ khả năng mở rộng (scalability) thường được dùng để chỉ việc liệu có thể thêm tính song song vào phần cứng hoặc phần mềm hay không, và liệu có giới hạn tổng thể cho sự cải thiện đó hay không.
Khả năng mở rộng bộ nhớ (Memory Scaling)
Mặc dù đa số mọi người quan tâm vào thời gian chạy, nhưng khả năng mở rộng bộ nhớ thường quan trọng hơn.
Giả sử ta vận hành một hệ thống siêu thị gồm 4 chi nhánh (tương đương 4 bộ xử lý – Proc). Để hệ thống hoạt động, mỗi chi nhánh cần quản lý hai loại dữ liệu sau:
- Loại 1: Danh sách giá sản phẩm (Bảng giá chung)
- Mọi chi nhánh đều phải bán cùng một mức giá. Khi khách hàng quét mã vạch, máy tính tại chi nhánh đó phải biết giá ngay lập tức mà không cần hỏi chi nhánh khác.
- Cách lưu trữ: Ta phải chép một bản danh sách giá đầy đủ vào máy tính của tất cả 4 chi nhánh.
- Đặc điểm: Nếu danh sách giá tăng lên (từ 1.000 mặt hàng lên 1.000.000 mặt hàng), thì máy tính tại mỗi chi nhánh đều phải tăng dung lượng lưu trữ tương ứng để chứa hết đống dữ liệu đó.
- Loại 2: Danh sách khách hàng thân thiết
- Hệ thống có 4.000 khách hàng. Để giảm tải, ta chia ra: chi nhánh A giữ thông tin 1.000 người đầu tiên, chi nhánh B giữ 1.000 người tiếp theo, tương tự cho C và D.
- Cách lưu trữ: Dữ liệu được chia nhỏ và phân chia cho các chi nhánh.
- Đặc điểm: Nếu số lượng khách hàng tăng lên 16.000 người, ta chỉ cần mở thêm 12 chi nhánh nữa (tổng 16 chi nhánh). Lúc này, mỗi máy tính chi nhánh vẫn chỉ phải quản lý đúng 1.000 khách hàng như cũ.
Từ ví dụ trên, ta chuyển dịch sang các thuật ngữ chuyên môn trong hình ảnh:
- Mảng sao chép (Replicated Array – R) – Tương ứng với “Bảng giá chung”
- Định nghĩa: Là tập dữ liệu mà mỗi bộ xử lý (Proc) bắt buộc phải giữ một bản sao toàn phần.
- Tại sao phải thế? Trong tính toán song song, có những dữ liệu mà mọi bộ xử lý đều cần truy cập liên tục (ví dụ: hằng số vật lý, bản đồ địa hình, bảng tra cứu). Việc sao chép giúp Proc lấy dữ liệu ngay lập tức từ bộ nhớ cục bộ (Local Memory), không phải chờ đợi truyền tin qua mạng giữa các Proc khác, giúp tăng tốc độ xử lý.
- Mảng phân tán (Distributed Array – D) – Tương ứng với “Danh sách khách hàng”
- Định nghĩa: Là tập dữ liệu khổng lồ được chia nhỏ. Mỗi Proc chỉ giữ một phần (segment).
- Tại sao phải thế? Để xử lý các bài toán cực lớn (Big Data) mà một máy đơn lẻ không bao giờ chứa nổi. Ta tận dụng tổng dung lượng RAM của nhiều máy cộng lại.
![[A.I System Programming] B4: Motivation của GPU 16 image 88 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-88-1024x358.png)
Hãy nhìn lại thông số trong hình:
- Ở 1 Proc: Ta có mảng R (100MB) và mảng D (100MB). Tổng cộng tốn 200MB RAM.
- Ở 4 Proc (Quy mô bài toán tăng gấp 4):
- Mảng D được chia cho 4 máy, nên mỗi máy vẫn chỉ gánh 100MB (Tổng 400MB). Đây là tin tốt.
- Vấn đề nằm ở mảng R: Vì tính chất sao chép, khi tổng dữ liệu R tăng lên 400MB, thì từng máy một đều phải gánh đủ 400MB.
- Kết quả: Một máy bây giờ phải chứa 400MB (R) + 100MB (D) = 500MB.
Mảng D (Phân tán): Khi quy mô bài toán tăng gấp 4 (từ 100 MB lên 400 MB tổng cộng) và số Proc tăng lên 4, lượng dữ liệu mảng D mà mỗi Proc phải xử lý vẫn giữ nguyên là 100 MB. Đây là trạng thái mở rộng lý tưởng.
Mảng R (Sao chép): Vì tính chất phải sao chép toàn bộ, khi kích thước bài toán tăng lên 400 MB, mọi Proc đều phải chứa đủ 400 MB.
Nếu máy chỉ có 450MB RAM, thì coi như bây giờ ta vẫn luôn có một giới hạn trên cho việc tăng số lượng process là các dữ liệu bắt buộc phải copy tại mọi máy. Dù ta có mua thêm 100 hay 1000 máy nữa cũng vô dụng, vì mỗi cái máy đơn lẻ đó đều không đủ RAM để chứa mảng R đang phình to.
- Thời gian chạy (Runtime): Nếu thuật toán dở, ta thêm máy thì nó chạy nhanh hơn hoặc chậm đi một chút, nhưng nó vẫn chạy được.
- Bộ nhớ (Memory): Nếu thiết kế mảng sao chép (R) quá nhiều, khi bài toán lớn đến một mức nhất định, nó sẽ không thể chạy được vì vượt ngưỡng RAM vật lý của từng nút xử lý.
Tính toán song song hoạt động như thế nào?
Tính toán song song đòi hỏi sự kết hợp giữa hiểu biết về phần cứng, phần mềm và tính song song để phát triển ứng dụng. Với một số khái niệm như (CPU/Process/Thread/…) nếu bạn chưa biết, phần này sẽ được đề cập sơ lược qua ở các phần sau khi đi sâu hơn.
Ta thấy rằng giữa mã nguồn và phần cứng, ứng dụng phải đi qua các lớp bổ sung, bao gồm trình biên dịch (compiler) và hệ điều hành (OS).
![[A.I System Programming] B4: Motivation của GPU 17 image 89 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-89.png)
Chúng ta phân loại các phương pháp tiếp cận song song có thể thực hiện thành:
- Song song hóa dựa trên tiến trình (Process-based): Mỗi tiến trình có không gian bộ nhớ riêng.
- Song song hóa dựa trên luồng (Thread-based): Các luồng chia sẻ chung bộ nhớ.
- Vector hóa (Vectorization): Thực hiện một lệnh trên nhiều dữ liệu.
- Xử lý dòng (Stream processing): Thường gắn liền với GPU.
Chúng ta sẽ sử dụng phương pháp song song hóa dữ liệu (data parallel) trên một lưới không gian hai chiều (2D). Ví dụ minh họa là hình ảnh 2D của núi lửa Krakatau
Để hiểu tại sao chúng ta cần những khái niệm này, hãy đi từ bài toán thực tế: Dự báo sóng thần sau khi núi lửa Krakatau phun trào.
![[A.I System Programming] B4: Motivation của GPU 18 image 90 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-90.png)
Bước 1: Rời rạc hóa vấn đề thành các ô hoặc phần tử nhỏ hơn
Trước khi tính toán chi tiết, ta phải chia nhỏ miền vấn đề thành các mảnh nhỏ, quá trình này gọi là rời rạc hóa (discretization). Trong xử lý ảnh, đây chính là các pixel. Trong mô phỏng, chúng được gọi là các ô (cells) hoặc phần tử (elements).
![[A.I System Programming] B4: Motivation của GPU 19 image 91 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-91.png)
Nước biển là một khối liền mạch. Nhưng máy tính chỉ có thể lưu trữ dữ liệu dưới dạng các con số riêng biệt trong bộ nhớ (RAM).
- Việc cần làm: Ta đặt một lưới (grid) lên bản đồ vùng biển Krakatau. Mỗi ô vuông (ô đơn vị) sẽ đại diện cho một vị trí.
- Dữ liệu lưu trữ: Tại mỗi ô, ta lưu một con số cụ thể như: Độ cao mực nước hiện tại.
- Ô gần núi lửa: 10 mét (đang có sóng lớn).
- Ô xa núi lửa: 0 mét (nước đang lặng).
Bước 2: Xác định hạt nhân tính toán (kernel) hoặc thao tác trên mỗi phần tử
Hãy tưởng tượng tại giây thứ 1, ô A có sóng cao 10m, ô B bên cạnh có sóng cao 0m.
- Câu hỏi: Ở giây thứ 2, chuyện gì xảy ra với ô B?
- Thực tế: Nước từ ô A sẽ tràn sang ô B. Độ cao nước ở ô B sẽ tăng lên.
Nếu ta chỉ tính toán “cô lập” (chỉ nhìn vào ô B mà không nhìn xung quanh), máy tính sẽ thấy ô B đang là 0m và mãi mãi là 0m. Nó không hề biết có một con sóng cực lớn ngay bên cạnh sắp ập đến.
Để biết tương lai của một vị trí (ô trung tâm), ta bắt buộc phải xem xét tình trạng của các vị trí xung quanh (lân cận). Đây chính là sự tương tác vật lý.
Các phép tính trên dữ liệu rời rạc này thường ở dạng toán tử khuôn mẫu (stencil operation). Nó được gọi như vậy vì nó liên quan đến một mẫu các ô lân cận để tính toán giá trị mới cho ô trung tâm.
![[A.I System Programming] B4: Motivation của GPU 20 image 92 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-92.png)
Mô phỏng sự thay đổi (Wave Propagation): Trong bài toán Krakatau, việc dùng Stencil giúp máy tính tính toán sự lan truyền. Sóng từ ô này truyền sang ô kia qua từng bước thời gian.
- Bước 1: Tính toán dựa trên lân cận.
- Bước 2: Cập nhật giá trị mới.
- Bước 3: Lặp lại (Sóng sẽ di chuyển dần trên màn hình).
Bước 3: Vector hóa để xử lý nhiều đơn vị dữ liệu cùng lúc
Chúng ta bắt đầu giới thiệu tính song song bằng cách nhìn vào Vector hóa (Vectorization). Một số bộ vi xử lý có khả năng thao tác trên nhiều mảnh dữ liệu cùng một lúc; khả năng này được gọi là các thao tác vector.
![[A.I System Programming] B4: Motivation của GPU 21 image 93 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-93.png)
Bước 4: Luồng (Threads) để triển khai nhiều lộ trình tính toán
Vì hầu hết các CPU ngày nay có ít nhất 4 lõi xử lý, chúng ta sử dụng đa luồng (threading) để vận hành các lõi đồng thời trên bốn hàng cùng một lúc.
![[A.I System Programming] B4: Motivation của GPU 22 image 94 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-94.png)
Bước 5: Tiến trình (Processes) để trải rộng tính toán sang các không gian bộ nhớ riêng biệt
Chúng ta có thể chia nhỏ công việc hơn nữa giữa các bộ vi xử lý trên hai máy tính để bàn, thường được gọi là các nút (nodes) trong xử lý song song. Khi công việc được chia tách giữa các nút, không gian bộ nhớ cho mỗi nút là riêng biệt và độc lập.
![[A.I System Programming] B4: Motivation của GPU 23 image 95 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-95.png)
Ngay cả với kịch bản phần cứng khiêm tốn này, tiềm năng tăng tốc đã là 32 lần.
Mô hình phần cứng cho các hệ thống song song
Để hiểu cách tính toán song song vận hành, chúng ta cần nắm rõ các thành phần phần cứng:
- DRAM (Dynamic Random Access Memory): Bộ nhớ lưu trữ thông tin/dữ liệu.
- Lõi tính toán (Core): Thực hiện các phép tính số học và logic.
- Bộ xử lý (Processor/CPU): Được trang bị nhiều lõi.
- Thiết bị tăng tốc (Accelerator): Thường là GPU, với hàng nghìn lõi và không gian bộ nhớ riêng.
- Nút tính toán (Compute Node): Sự kết hợp của CPU, DRAM và thiết bị tăng tốc (ví dụ: một thùng máy đơn lẻ).
- Mạng kết nối (Interconnect): Liên kết các nút với nhau.
Kiến trúc bộ nhớ phân tán (Distributed memory): Cross Node
![[A.I System Programming] B4: Motivation của GPU 24 image 96 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-96.png)
Một trong những hướng tiếp cận đầu tiên và có khả năng mở rộng cao là một hệ thống bộ nhớ phân tán. Mỗi CPU thì sẽ có bộ nhớ riêng (DRAM) và kết nối với CPU khác. Có thể thấy kiến trúc này cho phép ta thêm nhiều node CPU hơn không giới hạn, do các memory của CPU đều độc lập.
Tradeoff của cách tiếp cận này là lập trình viên phía trên có thể phải tự quản lý, phân chia vùng nhớ bên dưới ngay từ đầu.
Kiến trúc bộ nhớ chia sẻ (Shared memory): On Node
![[A.I System Programming] B4: Motivation của GPU 25 01_16](https://learning.oreilly.com/api/v2/epubs/urn:orm:book:9781617296468/files/OEBPS/Images/01_16.png)
Một hướng tiếp cận khác là kết nối 2 CPU trực tiếp vào 1 vùng nhớ chung. Cách này cho phép nhiều CPU có chung một vùng địa chỉ bộ nhớ, giúp lập trình đơn giản hơn. Hiển nhiên nó cũng đi kèm tradeoff như rủi ro về xung đột bộ nhớ, ví dụ như 2 chương trình trên 2 CPU tranh chấp tài nguyên trên cùng 1 địa chỉ, ảnh hưởng đến độ chính xác hay hiệu năng. Việc đồng bộ các thao tác truy cập giá trị của bộ nhớ giữa các CPU có thể sẽ khá phức tạp và tốn kém.
Vector units: Nhiều thao tác trong cùng một instruction
Như đã nói ở đầu bài, ta không thể đơn giản là tăng tần số xung nhịp (clock frequency) của bộ vi xử lý để đạt được năng suất xử lý (throughput) cao hơn như cách ta đã làm trong quá khứ nữa. Trở ngại lớn nhất trong việc tăng tần số xung nhịp CPU là việc này đòi hỏi nhiều điện năng hơn và sinh ra nhiều nhiệt hơn.
1. Khái niệm Xung nhịp (Clock Frequency) và Chu kỳ (Cycle): Mọi tính toán trong CPU được điều phối bởi một bộ dao động thạch anh. Mỗi “nhịp” (tick) là một cơ hội để các bóng bán dẫn (transistor) chuyển trạng thái (đóng/mở) để xử lý dữ liệu.
- Vấn đề: Để tăng xung nhịp (ví dụ từ 2GHz lên 5GHz), các electron phải di chuyển nhanh hơn và các transistor phải đóng mở nhanh hơn.
2. Công thức Năng lượng tiêu thụ (The Power Wall):
Dù đó là một trung tâm siêu máy tính (HPC) với những giới hạn về hệ thống đường dây điện lắp đặt, hay là chiếc điện thoại di động của ta với dung lượng pin hữu hạn, tất cả các thiết bị ngày nay đều đối mặt với những giới hạn về năng lượng.
Điện năng tiêu thụ của một chip bán dẫn (P) tỷ lệ thuận với điện dung (C), bình phương điện áp (V^2) và tần số xung nhịp (f):
Khi ta tăng tần số (f), ta buộc phải tăng điện áp (V) để duy trì tính ổn định của tín hiệu điện. Vì V được bình phương, lượng nhiệt sinh ra tăng theo hàm mũ. Đến một ngưỡng nhất định, chúng ta không thể tản nhiệt kịp cho chip mà không làm nó nóng chảy. Đây gọi là Bức tường năng lượng (Power Wall).
Kết luận: Vì không thể làm cho một “nhịp” chạy nhanh hơn mãi mãi, kiến trúc sư máy tính buộc phải tìm cách làm được nhiều việc hơn trong cùng một nhịp.
Thay vì tăng tần số xung nhịp, tại sao ta không thực hiện nhiều hơn một hoạt động (operation) trong mỗi chu kỳ (cycle)? Đây chính là ý tưởng cốt lõi đằng sau cơ chế vector hóa trên nhiều dòng bộ vi xử lý hiện nay.
1. Xử lý Vô hướng (Scalar Processing – SISD): Trong kiến trúc truyền thống (Single Instruction, Single Data – SISD), mỗi chỉ thị (Instruction) chỉ tác động lên một cặp dữ liệu duy nhất.
- Cơ chế: Nếu ta muốn cộng 8 cặp số, CPU phải thực hiện 8 lần: Lấy lệnh cộng -> Lấy dữ liệu A1, B1 -> Cộng -> Lưu kết quả -> Lặp lại 8 lần.
- Chi phí: Mỗi lần thực hiện, CPU tốn năng lượng để “giải mã chỉ thị” (Instruction Decoding) và điều phối dòng dữ liệu. Với 8 phép cộng, ta tốn 8 lần chi phí giải mã.
Xử lý Vector (Vector Processing – SIMD): Vectorization dựa trên mô hình SIMD (Single Instruction, Multiple Data) – Một chỉ thị duy nhất điều khiển nhiều luồng dữ liệu cùng lúc.
- Cấu tạo phần cứng: Thay vì một bộ logic toán học (ALU – Arithmetic Logic Unit) hẹp, CPU trang bị các Vector Units có các thanh ghi (Registers) rất rộng (128-bit, 256-bit hoặc 512-bit).
- Cơ chế thực thi: Thay vì nạp một số 32-bit vào thanh ghi, CPU nạp một “mảng” gồm nhiều số 32-bit vào thanh ghi rộng này. Khi lệnh
ADD(Cộng) được phát ra, Vector Unit sẽ kích hoạt nhiều ALU song song để cộng toàn bộ các cặp số đó trong đúng một chu kỳ xung nhịp.
Việc viết mã để tận dụng SIMD rất phức tạp vì nó phụ thuộc vào kiến trúc tập lệnh của từng loại CPU (như AVX-512 của Intel hay NEON của ARM).
- Compiler Analysis: Trình biên dịch tự động phân tích các vòng lặp (loop) và cố gắng chuyển chúng thành lệnh Vector.
- Pragmas/Directives: Là các chỉ dẫn (ví dụ:
#pragma omp simd) có thể được chèn vào mã nguồn để ra lệnh cho trình biên dịch: “Tôi xác nhận đoạn mã này an toàn, hãy vector hóa nó”. Nếu không có các chỉ dẫn này, trình biên dịch thường chọn phương án an toàn là chạy tuần tự để tránh sai sót dữ liệu.
Nếu Vector tốt như vậy, tại sao chúng ta vẫn cần Scalar và tại sao không chạy mọi thứ song song trên vector?
Sự phụ thuộc dữ liệu (Data Dependency):
Cơ chế Vector chỉ hoạt động khi các phép tính độc lập với nhau.
- Nếu ta tính: A = B + C và D = A + E. Ta không thể dùng Vector để tính A và D cùng lúc vì giá trị của D phụ thuộc vào kết quả của A.
Cấu trúc dữ liệu không liên tục (Non-contiguous Memory): Để Vector Unit hoạt động hiệu quả, dữ liệu phải nằm sát nhau trong bộ nhớ (Memory) để nạp vào thanh ghi rộng một cách nhanh chóng. Nếu dữ liệu nằm rải rác, CPU mất nhiều thời gian để thu thập (Gather), làm triệt tiêu lợi thế về tốc độ.
Chi phí phần cứng và Độ phức tạp: Mở rộng Vector Unit (ví dụ lên 2048-bit) đòi hỏi một diện tích chip khổng lồ cho các thanh ghi và các đường dẫn dữ liệu. Nó cũng làm cho bộ tập lệnh (ISA) trở nên cực kỳ phức tạp cho các trình biên dịch (Compiler) để tối ưu hóa.
Hầu hết các CPU và GPU hiện đại đều có khả năng vector hóa hoặc các cơ chế hoạt động tương đương. Lượng dữ liệu được xử lý trong một chu kỳ xung nhịp được gọi là độ dài vector (vector length). Độ dài này phụ thuộc trực tiếp vào kích thước vật lý của các đơn vị vector có trên bộ vi xử lý.
Hiện nay, độ dài vector phổ biến nhất là 256-bit. Nếu dữ liệu đã được rời rạc hóa là các số thực dấu phẩy động 64-bit (kiểu double), thì ta có thể thực hiện đồng thời bốn phép toán dấu phẩy động dưới dạng một hoạt động vector đơn nhất (256 / 64 = 4).
![[A.I System Programming] B4: Motivation của GPU 26 image 97 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-97.png)
Các đơn vị phần cứng vector sẽ:
- Nạp (load) một khối dữ liệu vào cùng một lúc.
- Thực hiện đồng thời một hoạt động duy nhất trên toàn bộ khối dữ liệu đó.
- Sau đó lưu trữ (store) kết quả cuối cùng.
Thiết bị tăng tốc: Bộ vi xử lý bổ sung chuyên dụng
Thiết bị tăng tốc (accelerator device) là một thành phần phần cứng rời rạc được thiết kế để thực thi các tác vụ cụ thể với tốc độ cao. Thiết bị tăng tốc phổ biến nhất hiện nay là GPU (Graphics Processing Unit – Đơn vị xử lý đồ họa). Khi được sử dụng cho mục đích tính toán thuần túy thay vì hiển thị hình ảnh, thiết bị này đôi khi được gọi là GPGPU (General-Purpose Graphics Processing Unit – Đơn vị xử lý đồ họa đa mục đích).
Cấu tạo và hiệu năng của GPU
GPU chứa rất nhiều lõi xử lý nhỏ, được gọi là các bộ đa xử lý luồng (Streaming Multiprocessors – SMs). Mặc dù cấu trúc của một SM đơn giản hơn so với một lõi CPU, nhưng tổng hợp lại, các SM cung cấp một sức mạnh xử lý khổng lồ. Thông thường, ta sẽ tìm thấy một GPU tích hợp (integrated GPU) có kích thước nhỏ nằm ngay bên trong CPU.
GPU rời và chi phí kết nối
Hầu hết các máy tính hiện đại còn sở hữu một GPU rời (discrete GPU) riêng biệt, kết nối với CPU thông qua bus PCI (Peripheral Component Interface). Việc sử dụng bus này làm phát sinh chi phí kết nối giữa CPU và GPU (communication cost) đối với dữ liệu và các chỉ lệnh. Tuy nhiên, các card đồ họa rời thường mạnh mẽ hơn nhiều so với các đơn vị tích hợp sẵn, do chúng được thiết kế cho một mục đích rõ ràng hơn, và có nhiều không gian phát triển hơn.
![[A.I System Programming] B4: Motivation của GPU 27 image 98 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-98.png)
Trong các hệ thống cao cấp, các nhà sản xuất đã phát triển các công nghệ kết nối riêng để giảm thiểu chi phí truyền tải dữ liệu này, ví dụ:
- NVIDIA sử dụng công nghệ NVLink.
- AMD Radeon sử dụng công nghệ Infinity Fabric.
Bây giờ, ta hãy kết hợp tất cả các kiến trúc phần cứng khác nhau đã nêu vào một mô hình thống nhất. Trong mô hình này, hai nút (nodes) mỗi nút sở hữu hai CPU cùng chia sẻ bộ nhớ DRAM.
![[A.I System Programming] B4: Motivation của GPU 28 image 99 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-99-1024x416.png)
Đặc điểm của mô hình
- Mỗi CPU là một bộ vi xử lý đa nhân (dual-core) tích hợp sẵn GPU.
- Một GPU rời được kết nối thông qua bus PCI vào một trong các CPU.
- Mặc dù các CPU dùng chung bộ nhớ chính, nhưng chúng thường nằm trong các vùng Truy cập bộ nhớ không đồng nhất (Non-Uniform Memory Access – NUMA) khác nhau. Điều này có nghĩa là việc một CPU truy cập vào bộ nhớ của CPU thứ hai sẽ tiêu tốn nhiều tài nguyên (đắt đỏ) hơn so với việc truy cập vào bộ nhớ cục bộ của chính nó.
Tầng hệ điều hành
Phần mềm dành cho tính toán song song nhất thiết phải dựa trên cấu trúc phần cứng bên dưới, tuy nhiên nó vẫn là một thực thể tách biệt với phần cứng. Hệ điều hành (OS) đóng vai trò là giao diện trung gian kết nối giữa hai thực thể này.
Ta cần lưu ý rằng các hoạt động song song không tự nhiên phát sinh; thay vào đó, mã nguồn phải chỉ định rõ cách thức song song hóa công việc. Điều này thực hiện thông qua việc:
- Khởi tạo các tiến trình (processes) hoặc các luồng (threads).
- Chuyển giao (offloading) dữ liệu, công việc và chỉ lệnh sang một thiết bị tính toán chuyên dụng.
- Thực hiện thao tác trên các khối dữ liệu cùng một lúc.
Trước hết ta nên xác định các thành phần có thể song song hóa, quyết định kỹ thuật tối ưu nhất để vận hành song song. Dưới đây là bốn kỹ thuật phổ biến nhất:
- Song song hóa dựa trên tiến trình (Process-based parallelization): Chuyển tin nhắn (Message passing).
- Song song hóa dựa trên luồng (Thread-based parallelization): Chia sẻ dữ liệu qua bộ nhớ.
- Vector hóa (Vectorization): Thực hiện nhiều thao tác với một chỉ lệnh duy nhất. <- Đã được nêu qua ở trên
- Xử lý luồng (Stream processing): Thông qua các bộ vi xử lý chuyên dụng.
Song song hóa dựa trên tiến trình: Cơ chế chuyển tin nhắn (Message Passing)
Đơn vị thực thi: Tiến trình (Process)
Khi ta chạy một chương trình, Hệ điều hành (OS) tạo ra một Tiến trình. Một tiến trình không chỉ là mã nguồn; nó là một thực thể bao gồm:
- Address Space (Không gian địa chỉ): Một dải bộ nhớ RAM mà OS cấp riêng cho tiến trình đó.
- Registers & Program Counter: Các thanh ghi vật lý trên CPU lưu trữ trạng thái hiện tại của lệnh đang chạy.
- Instruction Pipeline: Luồng các lệnh mã máy đang chờ được CPU xử lý.
Cơ chế cô lập bộ nhớ (Memory Isolation)
Trong các hệ điều hành hiện đại, Tiến trình A không thể đọc hoặc ghi vào bộ nhớ của Tiến trình B. Đây là một tính năng bảo mật và ổn định. Nếu Tiến trình A bị lỗi (crash), nó không làm ảnh hưởng đến dữ liệu của Tiến trình B.
Vấn đề: Nếu ta muốn chia một bài toán lớn (ví dụ: nhân hai ma trận khổng lồ) cho 10 tiến trình cùng làm, làm sao Tiến trình 1 gửi kết quả bán thành phẩm cho Tiến trình 2 khi bộ nhớ của chúng hoàn toàn tách biệt?
Giải pháp: Cần một cơ chế để “copy” dữ liệu từ không gian bộ nhớ này sang không gian bộ nhớ khác. Đó chính là lý do Message Passing (Chuyển tin nhắn) ra đời.
Tại sao không cho các tiến trình dùng chung bộ nhớ? Vì giới hạn vật lý của phần cứng.
- Shared Memory (Bộ nhớ dùng chung): Nhiều nhân CPU cùng cắm vào một thanh RAM. Giới hạn là số lượng khe cắm RAM và băng thông đường truyền trên bo mạch chủ.
- Distributed Memory (Bộ nhớ phân tán): Đây là kiến trúc của các siêu máy tính hoặc cụm máy chủ (Cluster). Mỗi “Nút” (Node) là một máy tính hoàn chỉnh có CPU và RAM riêng. Các nút này nối với nhau qua mạng (Ethernet hoặc InfiniBand).
Trong kiến trúc phân tán, CPU ở Node A tuyệt đối không có cách nào chạm tới thanh RAM ở Node B về mặt vật lý. Cách duy nhất là đóng gói dữ liệu thành một Tin nhắn (Message) và gửi qua dây dẫn mạng.
![[A.I System Programming] B4: Motivation của GPU 29 image 100 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-100-1024x554.png)
Khái niệm Ranks và Không gian bộ nhớ
Trong mô hình này, ứng dụng của ta sẽ khởi tạo các tiến trình riêng biệt, được gọi là các ranks trong thuật ngữ chuyển tin nhắn. Mỗi tiến trình này có không gian bộ nhớ riêng và đường ống chỉ lệnh (instruction pipeline) riêng biệt.
Khi chạy một ứng dụng song song dùng MPI, ta khởi tạo N bản sao của cùng một chương trình.
- Để phân biệt chúng, MPI gán cho mỗi bản sao một con số định danh duy nhất, gọi là Rank, bắt đầu từ 0, 1, 2, …, N-1.
- Dựa vào Rank, lập trình viên viết logic: “Nếu tôi là Rank 0, tôi sẽ điều phối; nếu tôi là Rank 1, tôi sẽ tính toán phần dữ liệu này”.
Không gian Người dùng (User Space) vs. Nhân (Kernel Space)
Quá trình gửi tin nhắn diễn ra qua hai tầng:
- User Space: Nơi mã ứng dụng của ta chạy. Ta gọi hàm
MPI_Send(dữ liệu). - Kernel Space: Nhân của hệ điều hành quản lý phần cứng (Card mạng).
- Cơ chế: Dữ liệu được copy từ User Space xuống Kernel Space, sau đó Kernel đẩy dữ liệu qua Driver card mạng để sang máy khác.
Phân biệt Tiến trình (Phần mềm) và Lõi/Core (Phần cứng)
Thực tế 1 tiến trình không luôn chạy trên 1 lõi cố định.
- Core (Lõi): Là một mạch điện vật lý thực hiện tính toán.
- Scheduler (Trình điều phối của OS): Là một thuật toán trong nhân OS. Nó quyết định tiến trình nào được sử dụng lõi nào tại thời điểm nào.
Hiện tượng Context Switching (Chuyển đổi ngữ cảnh): Nếu ta có 4 lõi nhưng chạy 8 tiến trình, OS sẽ cho mỗi tiến trình chạy khoảng vài miligiây, sau đó tạm dừng (lưu trạng thái vào RAM), và nạp tiến trình khác vào lõi. Việc này diễn ra liên tục, tạo cảm giác 8 tiến trình chạy song song, nhưng thực tế chúng đang “chia sẻ” thời gian của lõi.
![[A.I System Programming] B4: Motivation của GPU 30 image 101 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-101-1024x576.png)
Trong tính toán hiệu năng cao (HPC), người ta thường dùng kỹ thuật Binding (Ràng buộc): Ép chặt Rank 0 vào Core 0, Rank 1 vào Core 1 để tránh lãng phí thời gian chuyển đổi ngữ cảnh của OS.
Tính toán Phân tán (Distributed Computing) vs. Tính toán Song song (Parallel Computing)
Mặc dù cả hai đều dùng nhiều thực thể tính toán, chúng khác nhau về mức độ kết nối (Coupling).
| Đặc điểm | Tính toán Song song (MPI) | Tính toán Phân tán (P2P, Web) |
| Mục tiêu | Tối ưu tốc độ hoàn thành 1 tác vụ đơn lẻ. | Tối ưu độ tin cậy và khả năng phục vụ từ xa. |
| Giao tiếp | Tightly-coupled (Kết nối chặt): Các tiến trình phụ thuộc nhau rất lớn, tốc độ mạng cực nhanh (micro-giây). | Loosely-coupled (Kết nối lỏng): Các tiến trình độc lập hơn, có thể nằm cách xa nhau địa lý, chịu được độ trễ mạng cao. |
| Cơ chế | Sử dụng thư viện chuyên dụng như MPI. | Sử dụng các giao thức hệ thống như RPC (Remote Procedure Call) hoặc IPC (Inter-process communication) qua mạng. |
Ví dụ: Trong dự án, máy tính nhận một khối dữ liệu, xử lý trong 1 tiếng, sau đó gửi kết quả về máy chủ. Nếu máy tắt đột ngột, hệ thống vẫn chạy bình thường. Trong MPI, nếu một Rank bị lỗi, thường toàn bộ ứng dụng song song sẽ dừng lại để đảm bảo tính toàn vẹn dữ liệu.
Song song hóa dựa trên luồng: Chia sẻ dữ liệu qua bộ nhớ
Khi ta chạy một chương trình (process), hệ điều hành cấp cho nó một không gian bộ nhớ riêng biệt (Address Space). Không gian này chứa mã lệnh, dữ liệu và các tài nguyên khác.
Việc khởi tạo một Tiến trình mới rất tốn kém về tài nguyên hệ thống vì hệ điều hành phải cấp phát một vùng nhớ hoàn toàn độc lập. Luồng ra đời để giải quyết vấn đề này. Một luồng là một đơn vị thực thi nhỏ nhất nằm bên trong một tiến trình.
- Con trỏ chỉ lệnh (Instruction Pointer – IP): Đây là một thanh ghi trong CPU lưu trữ địa chỉ của lệnh tiếp theo sẽ được thực thi. Trong một tiến trình đơn luồng, chỉ có một IP. Để thực hiện song song, ta khởi tạo nhiều IP chạy đồng thời trên các nhân CPU khác nhau, mỗi IP đại diện cho một luồng.
- Cơ chế chia sẻ bộ nhớ: Vì tất cả các luồng thuộc cùng một tiến trình, chúng nhìn thấy chung một không gian địa chỉ. Điều này có nghĩa là luồng A có thể ghi một giá trị vào biến
xvà luồng B có thể đọc giá trị đó ngay lập tức mà không cần qua các bước chuyển giao dữ liệu phức tạp. Tuy nhiên điều này cũng tạo ra 1 vấn đề mới.
![[A.I System Programming] B4: Motivation của GPU 31 image 102 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-102-944x1024.png)
Khi hai luồng cùng truy cập và sửa đổi một vùng nhớ đồng thời, kết quả cuối cùng phụ thuộc vào việc luồng nào chạy nhanh hơn (Race Condition). Đây là lỗi logic cực kỳ khó phát hiện. Để xử lý, thông thường cần tới các cơ chế đồng bộ hóa (như locks hoặc barriers) để đảm bảo tại một thời điểm chỉ một luồng được thay đổi dữ liệu quan trọng.
Xử lý luồng thông qua bộ vi xử lý chuyên dụng (GPU)
Xử lý luồng (Stream processing) là một khái niệm về luồng dữ liệu (dataflow), trong đó một luồng dữ liệu được xử lý bởi một bộ vi xử lý chuyên dụng có cấu tạo đơn giản hơn. Kỹ thuật này từ lâu đã được sử dụng trong tính toán nhúng, sau đó được điều chỉnh để kết xuất các tập hợp vật thể hình học lớn cho màn hình máy tính trong một bộ vi xử lý chuyên dụng là GPU.
Từ xử lý đồ họa đến GPGPU
Các GPU này được thiết kế với một tập hợp rộng các phép toán số học và nhiều bộ đa xử lý luồng (SMs) để xử lý dữ liệu hình học song song. Vậy cụ thể GPU đã làm gì để thực hiện tốt hơn?
Đường ống lệnh (Instruction Pipeline)
Trước hết xem xét ở kiến trúc CPU, một lệnh (instruction) đi vào CPU được chia thành các giai đoạn (Stages):
- Fetch (Lấy lệnh): Lấy lệnh từ bộ nhớ.
- Decode (Giải mã): Hiểu lệnh đó yêu cầu làm gì.
- Execute (Thực thi): ALU thực hiện phép toán.
- Write-back (Ghi kết quả): Lưu kết quả vào thanh ghi.
Các mạch này có thực hiện việc nối đầu mạch. Ta thấy nếu chạy theo tuần tự:
- Chu kỳ 1: Mạch A chạy. Mạch B, C, D đứng im (Idle).
- Chu kỳ 2: Mạch B chạy. Mạch A, C, D đứng im.
- … Cứ thế, tại một thời điểm chỉ có 25% phần cứng CPU hoạt động.
Để tối ưu (Pipelining): Khi lệnh số 1 chuyển sang Mạch B, CPU lập tức đẩy lệnh số 2 vào Mạch A. Mục tiêu là để tất cả các mạch A, B, C, D đều có việc để làm trong mỗi chu kỳ xung nhịp.
Hiện tượng này xảy ra khi một mạch ở phía sau không thể thực hiện việc của mình vì nó thiếu thông tin từ mạch phía trước. Tuy nhiên tại 1 số thời điểm, hiện tượng Stall có thể xảy ra
Giả sử ta có 2 lệnh:
Lệnh 1: X = 5 + 5Lệnh 2: Y = X * 2
Tiến trình trong đường ống:
- Chu kỳ 1: Mạch A lấy Lệnh 1.
- Chu kỳ 2: Mạch B giải mã Lệnh 1 | Mạch A lấy Lệnh 2.
- Chu kỳ 3: Mạch C tính
5+5cho Lệnh 1 | Mạch B giải mã Lệnh 2.- Vấn đề: Mạch B giải mã Lệnh 2 và thấy nó cần giá trị
X. Nhưng lúc này Mạch C mới đang tínhX, chưa có kết quả cuối cùng.
- Vấn đề: Mạch B giải mã Lệnh 2 và thấy nó cần giá trị
- Chu kỳ 4: Mạch B (giải mã Lệnh 2) buộc phải phát tín hiệu “Dừng”. Mạch A cũng phải dừng theo vì không có chỗ để đẩy lệnh mới vào.
- Kết quả: Mạch C và D vẫn chạy nốt Lệnh 1, nhưng Mạch A và B bị Trống (Stall). CPU tốn một chu kỳ xung nhịp mà không tạo ra kết quả hữu ích nào
Giả sử có lệnh điều kiện: Nếu (A > B) thì nhảy đến Lệnh X, nếu không thì chạy tiếp Lệnh Y.
- Tại giai đoạn Fetch (Mạch A), CPU nạp lệnh tiếp theo theo thứ tự mặc định là
Lệnh Y. - Nhưng phải đợi 2 chu kỳ sau, khi lệnh
Nếu (A > B)đi đến giai đoạn Execute (Mạch C), CPU mới biết kết quả là đúng hay sai.
Để tối ưu, CPU sử dụng một bộ phận gọi là Branch Predictor (dựa trên lịch sử các lần chạy trước) để “đoán” xem nhánh nào sẽ được chọn và nạp sẵn các lệnh của nhánh đó vào đường ống. Nếu đoán sai, CPU buộc phải hủy toàn bộ các lệnh đã nạp nhầm, xóa sạch đường ống (Pipeline Flush) và bắt đầu lại từ điểm rẽ nhánh đúng.
Ngoài ra CPU cũng thực hiện Out-of-order Execution (OoOE – Thực thi ngoài tuần tự). Trong một chương trình, các lệnh thường có sự phụ thuộc lẫn nhau.
- Xét 2 lệnh:
A = Lấy dữ liệu từ RAM(Mất 200 nhịp)B = A + 5(Phụ thuộc vào A)C = 10 * 2(Không phụ thuộc vào A hay B) Nếu chạy tuần tự, lệnh số 3 phải đợi lệnh số 1 và 2 hoàn thành, dù nó chẳng liên quan gì. CPU sẽ đứng im chờ dữ liệu từ RAM.
- Cơ chế OoOE: CPU có một bộ đệm gọi là Instruction Window thực hiện quét các lệnh sắp tới, phân tích xem lệnh nào đã có đủ dữ liệu đầu vào. Trong ví dụ trên, CPU thấy lệnh số 3 đã sẵn sàng, nó sẽ đẩy lệnh 3 lên thực thi trước trong khi lệnh 1 vẫn đang chờ RAM. Sau khi có kết quả, nó sẽ sắp xếp lại (Reorder Buffer) để đảm bảo kết quả cuối cùng đúng như thứ tự.
Ngược lại, GPU được thiết kế cho Parallelism (Song song hóa), không phải Latency (Độ trễ).
GPU sử dụng cơ chế Hardware Multithreading. Nếu một nhóm nhân (thread) đang chờ dữ liệu từ RAM, GPU ngay lập tức tráo một nhóm nhân khác vào để tính toán. Nó không cố gắng làm một lệnh chạy nhanh hơn, nó chỉ cố gắng làm nhiều lệnh nhất có thể cùng lúc.
- Kiến trúc CPU: Dành 1 phần diện tích chip cho ALU tính toán, phần còn lại dành cho Control Logic (Branch Prediction, OoOE) và Cache
- Kiến trúc GPU: Loại bỏ hầu hết Branch Prediction và OoOE. Thay vào đó, GPU dùng diện tích đó để tập trung cho các nhân tính toán ALU
![[A.I System Programming] B4: Motivation của GPU 32 image 103 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-103.png)
Dữ liệu và hạt nhân tính toán (kernel) được hiển thị là được chuyển giao (offloaded) qua bus PCI sang GPU để tính toán. GPU vẫn còn hạn chế về chức năng so với CPU, nhưng ở những nơi có thể sử dụng các chức năng chuyên dụng này, chúng cung cấp khả năng tính toán phi thường với yêu cầu năng lượng thấp hơn. Các bộ vi xử lý chuyên dụng khác cũng thuộc danh mục này, mặc dù ta sẽ tập trung chủ yếu vào GPU trong các cuộc thảo luận của mình.
Phân loại các cách tiếp cận song song
Trong quá trình tìm hiểu về tính toán song song, ta sẽ bắt gặp các từ viết tắt như SIMD (một chỉ lệnh, nhiều dữ liệu) và MIMD (nhiều chỉ lệnh, nhiều dữ liệu). Những thuật ngữ này thuộc về các danh mục kiến trúc máy tính do Michael Flynn đề xuất vào năm 1966, được biết đến với tên gọi Phân loại của Flynn (Flynn’s Taxonomy).
Hệ thống phân loại này giúp ta nhìn nhận khả năng mà có thể song song hóa trong các kiến trúc theo những cách khác nhau. Việc phân loại dựa trên việc chia nhỏ các chỉ lệnh (instructions) và dữ liệu (data) thành các hoạt động tuần tự hoặc song song
![[A.I System Programming] B4: Motivation của GPU 33 image 104 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-104.png)
- SISD (Single Instruction, Single Data): Kiến trúc tuần tự truyền thống, một chỉ lệnh xử lý một dữ liệu.
- MISD (Multiple Instruction, Single Data): Trường hợp có nhiều chuỗi chỉ lệnh cùng xử lý một luồng dữ liệu duy nhất. Đây không phải là một kiến trúc phổ biến; ví dụ điển hình nhất là việc tính toán dư thừa (redundant computation) trên cùng một dữ liệu. Phương pháp này được sử dụng trong các hệ thống đòi hỏi khả năng chịu lỗi cực cao như bộ điều khiển tàu vũ trụ. Do tàu vũ trụ hoạt động trong môi trường bức xạ cao, chúng thường chạy hai bản sao của mỗi phép tính và so sánh kết quả đầu ra của cả hai.
- SIMD (Single Instruction, Multiple Data): Vector hóa là ví dụ điển hình, trong đó cùng một chỉ lệnh được thực hiện trên nhiều dữ liệu khác nhau. Một biến thể của SIMD là SIMT (Single Instruction, Multi-Thread – Một chỉ lệnh, nhiều luồng), thường được dùng để mô tả các nhóm làm việc (work groups) của GPU.
- MIMD (Multiple Instruction, Multiple Data): Danh mục cuối cùng có tính song song ở cả chỉ lệnh và dữ liệu. Danh mục này mô tả các kiến trúc song song đa lõi, chiếm đa số trong các hệ thống song song quy mô lớn hiện nay.
Cần lưu ý rằng mặc dù hệ thống phân loại này rất hữu ích, một số kiến trúc và thuật ngữ toán (algorithms) không khớp hoàn toàn vào một danh mục nhất định. Sự hữu dụng của nó nằm ở việc giúp ta nhận diện các đặc điểm của từng loại, chẳng hạn như danh mục SIMD thường gặp khó khăn với các câu lệnh điều kiện (conditionals). Nguyên nhân là vì mỗi mục dữ liệu có thể muốn thực thi một khối mã khác nhau, nhưng tất cả các luồng lại bắt buộc phải thực thi cùng một chỉ lệnh.
Các chiến lược song song
Ở ví dụ trước đó về núi lửa, ta đã xem xét việc song song hóa dữ liệu cho các ô lưới (cells) hoặc điểm ảnh (pixels). Tuy nhiên, song song hóa dữ liệu còn có thể áp dụng cho các hạt (particles) và các đối tượng dữ liệu khác.
Song song hóa dữ liệu (Data Parallelism)
Đây là cách tiếp cận phổ biến nhất và thường là đơn giản nhất. Về cơ bản, mỗi tiến trình thực thi cùng một chương trình nhưng vận hành trên một tập con dữ liệu duy nhất, như được minh họa:
![[A.I System Programming] B4: Motivation của GPU 34 image 105 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-105.png)
Ưu điểm của song song hóa dữ liệu là khả năng mở rộng tốt khi kích thước vấn đề và số lượng bộ vi xử lý tăng lên.
Song song hóa tác vụ (Task Parallelism)
Một cách tiếp cận khác là song song hóa tác vụ. Các chiến lược này bao gồm:
- Điều khiển chính – Main-worker): Một bộ vi xử lý đóng vai trò điều phối, lập lịch và phân phối các tác vụ cho tất cả các bộ vi xử lý worker. Mỗi worker sẽ kiểm tra mục công việc tiếp theo ngay khi nó trả lại kết quả của tác vụ đã hoàn thành trước đó.
- Đường ống (Pipeline): Chiến lược này được sử dụng trong các bộ vi xử lý siêu cấp (superscalar processors), nơi các phép tính địa chỉ và số nguyên được thực hiện bằng một đơn vị logic riêng biệt thay vì dùng bộ xử lý số thực dấu phẩy động, cho phép các phép tính này được thực hiện song song.
- Tiếp sức/Dây chuyền (Bucket-brigade): Sử dụng mỗi bộ vi xử lý để vận hành và biến đổi dữ liệu trong một chuỗi các hoạt động liên tiếp.
Ta cũng có thể kết hợp các chiến lược song song khác nhau để khai thác mức độ song song hóa lớn hơn cho ứng dụng.
![[A.I System Programming] B3: Lịch sử của máy tính 36 image 59 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-59.png)
![[A.I System Programming] B3: Lịch sử của máy tính 37 image 60 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-60.png)
![[A.I System Programming] B3: Lịch sử của máy tính 38 image 61 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-61.png)
![[A.I System Programming] B3: Lịch sử của máy tính 39 image 62 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-62.png)
![[A.I System Programming] B3: Lịch sử của máy tính 40 image 63 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-63.png)
![[A.I System Programming] B3: Lịch sử của máy tính 42 image 65 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-65-1024x415.png)
![[A.I System Programming] B3: Lịch sử của máy tính 43 image 67 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-67.png)
![[A.I System Programming] B3: Lịch sử của máy tính 44 image 69 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-69.png)
![[A.I System Programming] B3: Lịch sử của máy tính 45 image 70 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-70.png)
![[A.I System Programming] B3: Lịch sử của máy tính 46 image 72 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-72.png)
![[A.I System Programming] B3: Lịch sử của máy tính 47 image 71 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-71.png)
![[A.I System Programming] B3: Lịch sử của máy tính 48 image 73 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-73.png)
![[A.I System Programming] B3: Lịch sử của máy tính 49 image 74 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-74.png)
![[A.I System Programming] B3: Lịch sử của máy tính 50 image 75 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-75.png)
![[A.I System Programming] B3: Lịch sử của máy tính 51 image 77 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-77.png)
![[A.I System Programming] B3: Lịch sử của máy tính 52 image 78 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-78.png)
![[A.I System Programming] B3: Lịch sử của máy tính 53 image 76 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-76.png)
![[A.I System Programming] B3: Lịch sử của máy tính 54 image 79 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-79.png)
![[A.I System Programming] B3: Lịch sử của máy tính 55 image 80 - quochung.cyou PTIT](https://quochung.cyou/wp-content/uploads/2026/03/image-80.png)











































