Tất cả bài viết Ảnh bìa: Bên trong GPU: vì sao kiến trúc này hợp với nhân ma trận?
16 tháng 7, 2026 · 16 phút đọc

Bên trong GPU: vì sao kiến trúc này hợp với nhân ma trận?

Đi từ SM, warp và hệ thống bộ nhớ tới matrix tiling, xử lý ảnh và Transformer để hiểu GPU mạnh ở đâu, và vì sao nhiều core chưa bao giờ là toàn bộ câu chuyện.

GPUCUDAAI

Bạn viết một dòng C = A @ B, quạt GPU bắt đầu quay, rồi cả một khối phép nhân và cộng trôi qua trước khi mắt kịp chớp. Nhìn từ Python, chuyện ấy có vẻ gần như phép thuật. Nhìn từ transistor, nó là kết quả của một lựa chọn kiến trúc rất rõ: hy sinh khả năng xử lý một luồng lệnh phức tạp thật nhanh để đổi lấy throughput, tức hoàn thành một khối lượng lớn phép tính giống nhau trong cùng khoảng thời gian.

GPU không phải CPU có nhiều core hơn. Nó là một cỗ máy được tổ chức quanh dữ liệu song song, với hàng loạt cụm tính toán nhỏ, scheduler phần cứng và một hệ thống bộ nhớ được thiết kế để cung cấp dữ liệu liên tục cho các cụm tính toán ấy. Nhân ma trận, xử lý ảnh và Transformer hợp với thiết kế này vì chúng đều có thể tách thành rất nhiều phần việc gần như độc lập.

CPU tối ưu một thread, GPU tối ưu throughput

Một CPU hiện đại phải làm tốt đủ loại việc: đổi nhánh liên tục, xử lý interrupt, chạy hệ điều hành, dự đoán lệnh tiếp theo và giữ latency của một thread thật thấp. Vì vậy, mỗi core CPU dành nhiều diện tích silicon cho cache lớn, branch predictor, out-of-order execution và logic điều khiển.

GPU chọn hướng khác. Phần lớn diện tích được dành cho nhiều execution lane và rất nhiều register. Thay vì cố làm một thread đơn lẻ nhanh nhất, GPU giữ hàng nghìn thread sẵn sàng; khi một nhóm đang chờ dữ liệu từ bộ nhớ, scheduler chuyển sang nhóm khác. Thời gian chờ không biến mất, nó được che đi bằng công việc khác.

Đây cũng là lý do nói “GPU có hàng nghìn core” dễ gây hiểu nhầm. Một “CUDA core” không tương đương một core CPU hoàn chỉnh. Đơn vị nên nhìn vào là cả cụm tính toán: NVIDIA gọi là Streaming Multiprocessor (SM), còn AMD gọi là Compute Unit (CU). Theo CUDA Programming Guide, mỗi SM có register file riêng, cùng các vùng nhớ/cache trên chip như shared memory và L1 cache; nhiều SM nối tới bộ nhớ GPU. Tài liệu ROCm mô tả CU bằng các pipeline vector, scalar, memory, Local Data Share (LDS) và scheduler tương ứng.12

Sơ đồ hệ thống bộ nhớ và các cụm SM hoặc CU bên trong GPU

Sơ đồ trên là mô hình logic, không phải floorplan của một GPU cụ thể. Tên gọi và số lượng unit thay đổi theo hãng, thế hệ và phân khúc, nhưng các mảnh ghép chính khá ổn định:

  • SM/CU nhận các block công việc và giữ nhiều nhóm thread resident cùng lúc.
  • Register file chứa biến tạm của từng thread, rất nhanh nhưng hữu hạn. Một kernel dùng quá nhiều register sẽ làm giảm số thread có thể cùng resident, tức giảm occupancy.
  • Shared memory/LDS là scratchpad nằm trên chip, do lập trình viên hoặc compiler chủ động dùng để chia sẻ và tái sử dụng dữ liệu trong một block.
  • L1/L2 cache giảm số lần phải đi tới VRAM. Cách tổ chức cụ thể khác nhau giữa các kiến trúc.
  • VRAM/HBM có băng thông cao và dung lượng lớn, nhưng access latency cao hơn register hay shared memory; khi dữ liệu chưa có sẵn trong cache, execution unit phải chờ global memory đáp ứng yêu cầu.

GPU mạnh khi các tầng này cùng chạy trơn tru. Nếu dữ liệu không được cung cấp kịp cho các lane, các lane ấy phải chờ dữ liệu và không thể thực hiện phép tính.

Warp: nhiều thread, một nhịp lệnh

Thread trên GPU không được phát lệnh hoàn toàn độc lập như những CPU core thu nhỏ. NVIDIA gom thread thành warp 32 thread và thực thi theo mô hình SIMT, Single Instruction, Multiple Threads. Các thread chạy cùng kernel nhưng giữ dữ liệu và trạng thái riêng. AMD dùng khái niệm wavefront; kích thước tùy dòng GPU, chẳng hạn tài liệu ROCm hiện nêu 64 thread trên AMD Instinct và 32 thread trên AMD Radeon.13

Hãy tưởng tượng 32 pixel nằm cạnh nhau cùng cần tăng độ sáng. Một lệnh cộng có thể được áp lên 32 giá trị trong warp. Bộ điều khiển lệnh không phải giải mã 32 luồng lệnh hoàn toàn khác nhau, nhờ vậy phần điều khiển gọn hơn và nhiều diện tích silicon hơn được dành cho các đơn vị tính toán.

Cái giá phải trả là branch divergence. Nếu 16 thread đi vào nhánh if còn 16 thread đi vào else, warp phải lần lượt xử lý các đường đi cần thiết và mask những lane không tham gia. CUDA Programming Guide lưu ý utilization cao nhất khi các thread trong warp đi cùng control-flow path.1 Bài toán có dữ liệu đều, ít nhánh và nhiều phần tử độc lập vì thế hợp GPU hơn một cây quyết định lằng nhằng.

Vì sao nhân ma trận hợp với GPU

Với C=ABC = AB, mỗi phần tử đầu ra là một dot product:

Cij=k=0K1AikBkjC_{ij} = \sum_{k=0}^{K-1} A_{ik}B_{kj}

Khi chưa xét tối ưu, mỗi cặp (i,j)(i,j) có thể giao cho một thread. Các phần tử đầu ra của CC có thể được tính độc lập với nhau; bên trong mỗi phần tử vẫn là một chuỗi tích lũy theo trục KK. Ma trận càng lớn càng tạo ra nhiều công việc để tận dụng GPU. Phép toán bên trong lặp đều theo chuỗi load, multiply, add, phù hợp với cách GPU phát cùng instruction cho nhiều thread.

Nếu mỗi thread tự đọc dữ liệu từ VRAM cho từng phép tính, cùng một phần tử của AA hoặc BB có thể bị đọc lặp lại nhiều lần. Khi kernel dành phần lớn thời gian chờ dữ liệu, tốc độ tính toán cao cũng không giúp được nhiều. Cách triển khai thực tế vì thế chia ma trận thành các tile nhỏ:

  1. Một thread block cùng nạp tile của AABB từ global memory vào shared memory.
  2. Các thread đồng bộ để chắc rằng tile đã sẵn sàng.
  3. Mỗi thread tái sử dụng dữ liệu trong tile để tích lũy một hay nhiều phần tử của CC.
  4. Các thread đồng bộ lần nữa trước khi shared memory được ghi đè bằng tile kế tiếp.
  5. Block lặp qua trục KK, rồi ghi kết quả ra global memory.

Ví dụ matrix tiling: tile của A và B được tái sử dụng trong shared memory để tính một tile của C

CUDA Best Practices Guide dùng chính phép nhân C=ABC = AB để minh họa: trong triển khai tiled, một thread block phối hợp nạp tile từ global memory vào shared memory theo access pattern coalesced, để các thread trong block tái sử dụng dữ liệu thay vì đọc lặp lại từ VRAM.4 Nếu mỗi thread nhảy tới một vùng xa nhau, băng thông thực tế sẽ tụt dù con số quảng cáo của VRAM không đổi.

Tiling tạo ra hai lợi thế cùng lúc: nhiều output độc lập để chạy song song, và nhiều phép tính trên mỗi byte đã đọc. Tỷ lệ sau thường được gọi là arithmetic intensity. Khi tile vừa với shared memory và phần dữ liệu mỗi thread giữ trong register, GPU có thể duy trì hoạt động của các execution unit và giảm số lần đọc dữ liệu từ VRAM.

Matrix core không thay thế phần còn lại của GPU

GPU AI hiện đại còn có unit chuyên cho matrix multiply-accumulate: NVIDIA gọi là Tensor Core, AMD dùng các matrix core/MFMA unit trên dòng phù hợp. Thay vì phát một chuỗi dài lệnh scalar, một instruction có thể mô tả phép nhân và tích lũy trên các mảnh ma trận nhỏ.

Whitepaper A100 mô tả Tensor Core thực hiện matrix multiply-accumulate và nêu GEMM, phép nhân ma trận tổng quát, là phần cốt lõi của huấn luyện lẫn inference mạng nơ-ron.5 Điều đáng chú ý không chỉ là số phép nhân mỗi chu kỳ. Một matrix instruction lớn còn giảm áp lực lên instruction fetch, scheduler và register traffic so với việc biểu diễn cùng công việc bằng nhiều lệnh nhỏ.

Dẫu vậy, Tensor Core chỉ thực hiện các phép matrix multiply-accumulate trên những operand đã được đưa tới đúng execution unit; nó không thay thế các bước load/store, indexing, activation hay kết hợp output của model. Vector unit vẫn xử lý indexing, activation và các phép element-wise; load/store unit vẫn chuyển dữ liệu; cache, shared memory và register vẫn phải đưa operand tới đúng lúc. Một kernel dùng sai layout hoặc tạo quá nhiều lần chuyển dữ liệu có thể khiến matrix core không nhận operand đủ nhanh để duy trì throughput.

Xử lý ảnh: một pixel, một thread

Trong máy tính, ảnh raster được biểu diễn như một lưới pixel. Ảnh grayscale có thể xem như một ma trận hai chiều, trong đó mỗi phần tử thường là giá trị từ 0 đến 255 biểu thị mức sáng: 0 là đen, 255 là trắng. Ảnh RGB phổ biến được biểu diễn bằng ba channel đỏ, xanh lá và xanh dương; mỗi channel có thể xem như một ma trận giá trị, và mỗi pixel được tạo từ ba giá trị tại cùng vị trí. Với các thao tác như đổi brightness, chuyển màu hay threshold, pixel đầu ra thường chỉ phụ thuộc vào pixel cùng vị trí hoặc một vùng lân cận cố định. Ta có thể ánh xạ gần như trực tiếp mỗi pixel vào một thread.

Lấy một ảnh grayscale 32×3232 \times 32 pixel và tăng sáng bằng cách nhân mỗi giá trị với 1,21{,}2. Ảnh có 1.024 pixel, nên kernel có thể khởi tạo 1.024 thread; thread mang chỉ số (x,y)(x,y) đọc đúng pixel (x,y)(x,y), thực hiện phép nhân rồi ghi kết quả về cùng vị trí. Trên GPU NVIDIA, 1.024 thread này được chia thành 32 warp, mỗi warp 32 thread. Warp scheduler chọn các warp đã sẵn sàng và phát lệnh của chúng tới execution unit, trong khi từng thread vẫn xử lý pixel riêng của mình.

Cách GPU ánh xạ ảnh 32 x 32 pixel thành 1.024 thread, chia thành 32 warp để tăng sáng đồng thời

Sơ đồ không có nghĩa cả 1.024 phép nhân nhất thiết hoàn tất trong đúng một nhịp clock. Số warp thực sự chạy cùng lúc còn phụ thuộc GPU, số SM, tài nguyên kernel và lịch thực thi. Ý quan trọng là giữa các pixel không có quan hệ phụ thuộc: GPU có sẵn 1.024 phần việc cùng loại để phát cho phần cứng, thay vì buộc một core xử lý lần lượt từ pixel đầu tới pixel cuối. Với ảnh thật cỡ 1920×10801920 \times 1080, con số ấy tăng lên 2.073.600 thread logic, tạo đủ thread để scheduler có thể phân việc cho nhiều SM.

Convolution phức tạp hơn vì mỗi pixel đầu ra đọc một vùng lân cận, chẳng hạn kernel 3×33 \times 3. Nhưng các vùng ấy chồng lấn mạnh: pixel cạnh nhau dùng lại gần như cùng một nhóm input. Đây lại là bài toán tiling. Một block nạp cả tile ảnh cùng phần viền, thường gọi là halo, vào shared memory; sau đó nhiều thread tái sử dụng tile để tính các pixel đầu ra.

GPU không phải lúc nào cũng nhanh hơn. Nếu các thread trong cùng warp thường xuyên rẽ vào những nhánh khác nhau, branch divergence sẽ làm một phần lane bị mask trong từng nhánh thực thi. Truy cập bộ nhớ không coalesced hoặc thiếu locality cũng làm giảm hiệu quả sử dụng bandwidth và cache. Những pipeline cần đồng bộ giữa nhiều block, hoặc phải chuyển dữ liệu qua lại giữa host và device ở mỗi bước, còn chịu thêm chi phí đồng bộ và truyền dữ liệu. GPU phát huy tốt hơn khi dữ liệu được giữ trên device qua nhiều kernel liên tiếp, các thread thường đi cùng nhánh điều khiển và phần lớn truy cập bộ nhớ có thể được gom thành transaction hiệu quả.

Trong Transformer, nhiều phép tính lớn được triển khai bằng GEMM

Một Transformer block chứa nhiều phép toán, nhưng phần compute lớn thường tập trung vào các linear projection và matrix multiplication. Với input XX, mô hình tạo query, key và value bằng ba phép chiếu:

Q=XWQ,K=XWK,V=XWVQ = XW_Q, \qquad K = XW_K, \qquad V = XW_V

Scaled dot-product attention trong paper gốc được viết như sau:6

Attention(Q,K,V)=softmax(QKTdk)V\operatorname{Attention}(Q,K,V) = \operatorname{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

QKTQK^T là matrix multiplication. Nhân attention weights với VV lại là một matrix multiplication. Hai linear layer trong feed-forward network của kiến trúc Transformer gốc cũng là GEMM. Khi gộp nhiều token và nhiều sequence thành batch, các ma trận tạo ra đủ parallel work để scheduler phân phối cho nhiều SM/CU, còn matrix core có cơ hội xử lý các tile ở precision phù hợp.

flowchart LR
    X["Input X"] --> QKV["Q, K, V projections<br/>GEMM"]
    QKV --> SCORE["QK^T / sqrt(d_k)<br/>GEMM"]
    SCORE --> SOFTMAX["Softmax"]
    SOFTMAX --> AV["Attention weights x V<br/>GEMM"]
    AV --> WO["Output projection W_O<br/>GEMM"]
    X --> RN1["Residual + normalization"]
    WO --> RN1
    RN1 --> FFN1["FFN linear 1<br/>GEMM + activation"]
    FFN1 --> FFN2["FFN linear 2<br/>GEMM"]
    RN1 --> RN2["Residual + normalization"]
    FFN2 --> RN2

Sơ đồ này là bản rút gọn: attention output còn đi qua phép chiếu WOW_O và một bước residual + normalization trước khi vào feed-forward network; sau feed-forward network lại có một bước residual + normalization nữa.6 Transformer không phải một phép nhân ma trận khổng lồ duy nhất. Nó là chuỗi kernel; giữa các kernel, intermediate tensor phải được đọc và ghi. Vì vậy các thư viện tối ưu thường fuse kernel để giữ dữ liệu gần execution unit hơn, giảm launch overhead và bớt traffic tới VRAM.

Ở training, batch và sequence thường tạo ma trận lớn, dễ khai thác throughput. Ở autoregressive inference, giai đoạn sinh từng token lại có shape khác: batch nhỏ và mỗi bước chỉ thêm một token, trong khi model weights rất lớn vẫn phải được đọc. Khi ấy workload có thể bị giới hạn bởi memory bandwidth hơn là số FLOP lý thuyết. Đây là lý do một GPU quảng cáo TFLOPS cao chưa chắc có tokens/second cao tương ứng cho mọi model và mọi batch.

Mixed precision cũng quan trọng. FP16, BF16 và các định dạng số nguyên có thể vừa tăng throughput của matrix unit vừa giảm lượng byte phải chuyển. TF32 chủ yếu tăng throughput Tensor Core cho dữ liệu lưu ở dạng FP32 bằng cách giảm precision của mantissa; nó không tự làm tensor nhỏ hơn.5 Mỗi datatype có trade-off riêng về sai số, range và, với số nguyên, quy trình quantization. Chọn datatype là quyết định của cả mô hình và kernel, không phải nút “nhanh miễn phí”.

GPU không mạnh ở mọi thứ

GPU sẽ không tự nhiên nhanh hơn nếu bài toán thiếu một trong các điều kiện mà kiến trúc cần. Những trường hợp thường làm giảm hiệu năng GPU gồm:

  • Công việc quá nhỏ: không đủ block hoặc warp để phân phối lên các SM và khai thác toàn bộ GPU, trong khi kernel launch và việc chuyển dữ liệu vẫn có chi phí cố định.
  • Nhiều nhánh phân kỳ: các lane trong warp không còn làm cùng việc hiệu quả.
  • Truy cập bộ nhớ rời rạc: các thread tạo ra nhiều transaction không coalesced, cache reuse thấp và việc nạp dữ liệu vào shared memory không còn giúp tái sử dụng hiệu quả.
  • Phụ thuộc tuần tự: bước sau phải chờ đúng kết quả của bước trước, không thể tách thành nhiều block độc lập.
  • Memory-bound: execution unit chờ dữ liệu dù FLOPS lý thuyết còn thừa.
  • Thiếu locality: tensor trung gian liên tục bị đẩy ra VRAM rồi đọc lại.

Bởi vậy, tối ưu GPU không chỉ là tăng số thread. Ta phải hỏi dữ liệu nằm ở đâu, được dùng lại bao nhiêu lần, các thread trong cùng warp có đi cùng nhánh không, register có làm occupancy tụt không, và shape có đủ lớn để thư viện chọn kernel tốt không.

GPU mạnh không phải vì từng phép nhân của nó thần kỳ. Nó mạnh vì kiến trúc có thể giao rất nhiều phép nhân độc lập cho các thread, gom thread thành warp để cùng phát một lệnh, duy trì nhiều warp ở trạng thái sẵn sàng để scheduler có thể phát lệnh khi execution unit hoặc dữ liệu đã sẵn sàng, tái sử dụng dữ liệu qua tile và chuyển những khối matrix math phù hợp cho unit chuyên dụng. Khi bài toán có cấu trúc ấy, các execution lane có việc để làm phần lớn thời gian. Khi bài toán không có, nhiều execution lane sẽ không được tận dụng hiệu quả.

Tài liệu tham khảo

  1. NVIDIA (2026). CUDA Programming Guide, Release 13.2. CUDA Documentation
  2. AMD (2026). Compute unit (CU). ROCm Compute Profiler Documentation
  3. AMD (2026). Device hardware glossary. ROCm 7.2.4 Documentation
  4. NVIDIA (2026). CUDA C++ Best Practices Guide: Shared Memory in Matrix Multiplication. CUDA Documentation
  5. NVIDIA (2020). NVIDIA A100 Tensor Core GPU Architecture. Architecture whitepaper
  6. Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Łukasz Kaiser, Illia Polosukhin (2017). Attention Is All You Need. NeurIPS 2017