Mở đầu: khi băng thông đã đủ, bức tường tiếp theo là tính toán
Bài 9 giải quyết việc đưa dữ liệu tới đúng chỗ nhanh nhất (UMA loại bỏ bước sao chép PCIe). Nhưng có dữ liệu nhanh chưa đủ — CPU vô hướng vẫn phải TÍNH TOÁN từng phần tử MỘT theo thứ tự. Đồ hoạ 3D và deep learning đều xoay quanh MỘT phép toán lõi: nhân ma trận. Bài này so sánh 3 kiến trúc phần cứng xử lý CÙNG phép toán đó với thông lượng khác biệt tới hàng nghìn lần.
1. Kiến trúc SIMD vs SIMT
SIMD (Single Instruction, Multiple Data) — CPU hiện đại có các thanh ghi vector rộng (vd AVX-256 xử lý 8 số 32-bit CÙNG lúc trong MỘT lệnh) — vẫn là kiến trúc scalar cơ bản, chỉ "nới rộng" từng lệnh để xử lý nhiều dữ liệu hơn. SIMT (Single Instruction, Multiple Threads) — cách GPU tổ chức: hàng nghìn Shader Core nhỏ, đơn giản, chạy CÙNG một lệnh nhưng trên hàng nghìn luồng dữ liệu độc lập song song — khác về BẢN CHẤT so với việc "nới rộng lệnh" của SIMD.
2. Kiến trúc NPU & Apple AMX
NPU (Neural Processing Unit) và Tensor Core là phần cứng CHUYÊN DỤNG chỉ để tăng tốc MỘT phép toán: nhân ma trận (và các biến thể tích chập convolution) — nhanh hơn hàng chục lần so với chạy phép toán tương đương trên CPU thông thường, nhờ thiết kế phần cứng CHỈ để làm một việc thay vì linh hoạt như CPU. Apple AMX (Apple Matrix Coprocessor) là khối đồng xử lý ma trận ẨN ngay trong lõi CPU Apple Silicon — không cần chuyển dữ liệu sang GPU (tiếp nối tinh thần UMA của Bài 9), CPU tự "gọi thẳng" tập lệnh AMX riêng khi chạy qua Accelerate framework hoặc CoreML.
SIMD (CPU vector, vd AVX-256):
1 lenh --> xu ly CUNG LUC 8 so 32-bit trong 1 thanh ghi rong
Van la 1 loi thuc thi (control flow), chi "no rong" DU LIEU
SIMT (GPU, vd CUDA/Metal):
1 lenh --> hang nghin Shader Core doc lap chay CUNG lenh do
MOI Shader Core co du lieu RIENG - that su nhieu LUONG thuc thi song song
(neu cac luong trong 1 nhom RE NHANH khac huong -> phai chay TUAN TU ca 2 nhanh)
Kien truc | Muc dich chinh | Vi du
---------------|------------------------------|---------------------------
CPU + SIMD | Da nang, phep toan da dang | AVX/AVX-512 (x86), NEON (ARM)
GPU (SIMT) | Song song HANG NGHIN luong | Do hoa, huan luyen mo hinh lon
NPU/TensorCore | Chuyen dung nhan ma tran | Suy luan AI tren thiet bi di dong
Apple AMX | Nhan ma tran AN trong CPU | Accelerate framework, CoreML
(khong can chuyen du lieu sang GPU - tiep noi tinh than UMA Bai 9)
3. Bài toán tính toán thông lượng FLOPs
Nhân 2 ma trận $N \times N$ theo thuật toán tuần tự kinh điển: mỗi trong $N^2$ phần tử kết quả cần $N$ phép nhân + $(N-1)$ phép cộng — tổng số phép tính dấu phẩy động (FLOPs):
$$\text{Total FLOPs} = N^2 \times (2N - 1) = 2N^3 - N^2$$
Verified thật: nhân ma trận 1024×1024 cần đúng 2.146.435.072 FLOPs (~2,15 tỷ). So sánh thời gian tính CÙNG phép toán này trên 3 kiến trúc — CPU vô hướng (4 GFLOPS), CPU SIMD (32 GFLOPS, tương đương AVX 8-wide = 8× scalar), và GPU (10 TFLOPS):
function matrixMultiplyFlops(n) {
return 2 * Math.pow(n, 3) - Math.pow(n, 2);
}
function computeTimeSeconds(flops, flopsPerSecond) {
return flops / flopsPerSecond;
}
// Verified: matrixMultiplyFlops(1024) = 2.146.435.072 FLOPs
// Verified: scalar (4 GFLOPS) -> 0,5366 giay
// Verified: SIMD (32 GFLOPS) -> 0,0671 giay (nhanh hon scalar DUNG 8 lan = 32/4)
// Verified: GPU (10 TFLOPS) -> 0,000215 giay = 0,215 ms (nhanh hon scalar 2500 lan)
SIMD nhanh hơn scalar đúng 8 lần — CHÍNH XÁC bằng độ rộng vector (không phải một con số ước lượng). GPU nhanh hơn scalar tới 2500 lần — chênh lệch khổng lồ này đến từ việc GPU có hàng nghìn Shader Core làm việc song song thật sự, khác hẳn việc chỉ "nới rộng" một lệnh như SIMD.
// Ma tran 4x4: chi 112 FLOPs - qua nho de GPU "dang" cong suc
const cmpSmall = compareComputeMethods(4, 4, 32, 10, 0.0001); // 0,1ms overhead GPU
// cmpSmall.scalarTimeSeconds ~ 0.000000028 s (khong overhead)
// cmpSmall.gpuTimeSeconds ~ 0.0001 s (GAN NHU TOAN BO la overhead, tinh toan that ~0)
// Verified: cmpSmall.gpuTimeSeconds > cmpSmall.scalarTimeSeconds -> GPU CHAM HON!
4. Mô hình Roofline: 10 TFLOPS chỉ có nghĩa nếu kịp nạp dữ liệu
Có một giả định lặng lẽ trong toàn bộ Mục 3 mà ta phải lôi ra ánh sáng: mọi con số thời gian ở trên đều tính bằng cách lấy FLOPs chia cho thông lượng ĐỈNH. Tức là ngầm giả định phần cứng luôn có sẵn dữ liệu để tính — băng thông bộ nhớ vô hạn. Không phần cứng nào như vậy. Muốn tính thì phải nạp dữ liệu vào đã, và với rất nhiều bài toán, chính bước nạp đó mới là thứ quyết định thời gian.
Mô hình Roofline (Williams, 2009) cân hai giới hạn đó với nhau bằng một đại lượng duy nhất: cường độ số học (arithmetic intensity) — số phép tính làm được trên mỗi byte đọc từ bộ nhớ.
Chia hai vế cho nhau được điểm gãy (ridge point) — cường độ tối thiểu để hết nghẽn băng thông. Với GPU 10 TFLOPS và băng thông 600 GB/s: $10\times10^{12} / 600\times10^{9} = \mathbf{16{,}67}$ FLOP/byte. Dưới mức đó là nghẽn bộ nhớ, trên mức đó là nghẽn tính toán.
Đặt hai phép toán lên cùng GPU đó. Nhân ma trận $N \times N$ làm $2N^3 - N^2$ phép tính nhưng chỉ cần chạm $3N^2$ phần tử — số phép tính tăng theo $N^3$ còn dữ liệu chỉ tăng theo $N^2$, nên càng lớn càng "lãi". Cộng vector thì ngược lại: mỗi phần tử 2 phép tính mà phải đọc x, đọc y, ghi y — tỷ lệ đó cố định, không cải thiện dù mảng to đến đâu.
const PEAK = 10e12; // 10 TFLOPS
const BW = 600e9; // 600 GB/s -> ridge point = 16.67 FLOP/byte
// Matrix multiply 1024x1024, FP32, each matrix touched once (ideal blocking)
rooflineAttainable(matmulArithmeticIntensity(1024, 4), PEAK, BW);
// AI = 170.58 -> { bound: 'compute', attainable: 10 TFLOPS, fractionOfPeak: 1.00 }
// Vector add y[i] = a*x[i] + y[i] on the SAME hardware
rooflineAttainable(vectorAddArithmeticIntensity(4), PEAK, BW);
// AI = 0.167 -> { bound: 'memory', attainable: 100 GFLOPS, fractionOfPeak: 0.01 }
// Same GPU. 100% of peak against 1% of peak. Peak FLOPS predicts neither.
Đây cũng là lý do nhân ma trận, chứ không phải phép toán nào khác, trở thành thứ mọi phần cứng AI đều đua nhau tăng tốc: nó là một trong số ít phép toán có cường độ số học tăng theo kích thước, nên càng đắp thêm FLOPS càng có chỗ dùng.
5. Phân kỳ warp: cái giá của một luồng lạc loài
Pitfall ở Mục 1 đã nói GPU "phải chạy tuần tự cả 2 nhánh" khi các luồng rẽ khác hướng. Hiện tượng đó có tên: phân kỳ warp (warp divergence), và đáng đo cho ra con số vì trực giác ở đây sai.
GPU không lập lịch từng luồng riêng lẻ. Nó gom thành nhóm — NVIDIA gọi là warp (32
luồng), AMD gọi là wavefront (64) — và cả nhóm dùng CHUNG một bộ đếm chương trình. Cả warp bắt buộc thực
hiện cùng một lệnh tại một thời điểm. Nên khi gặp if mà các luồng đi khác hướng, phần cứng
không có lựa chọn nào ngoài chạy nhánh then với các luồng còn lại bị TẮT, rồi chạy tiếp nhánh
else với nhóm kia bị tắt.
// All 32 threads take the same path
warpDivergence(new Array(32).fill(true));
// -> { passes: 1, efficiency: 1.00, wastedSlots: 0 }
// The warp splits evenly, 16 and 16
warpDivergence(Array.from({ length: 32 }, (_, i) => i < 16));
// -> { passes: 2, efficiency: 0.50, wastedSlots: 32 }
// ONE thread out of 32 goes the other way
warpDivergence(Array.from({ length: 32 }, (_, i) => i === 0));
// -> { passes: 2, efficiency: 0.50, wastedSlots: 32 } identical to the 16/16 split
Hệ quả thực tế:
if (threadId % 2 == 0) làm hỏng MỌI warp, còn
if (threadId / 32 % 2 == 0) — cùng chia đôi số luồng — thì không warp nào phân kỳ, vì ranh
giới rơi đúng vào biên warp. Cùng một điều kiện logic, cùng số luồng mỗi nhánh, hiệu năng khác nhau gấp
đôi. Đó là lý do lập trình GPU quan tâm tới việc luồng nào nằm cùng warp với luồng nào, chứ không chỉ
quan tâm thuật toán.
6. Thực hành: So sánh nhân ma trận Scalar vs SIMD vs GPU/AMX
Đổi kích thước ma trận và thông số 3 kiến trúc để xem thời gian tính thay đổi trực tiếp — thử giảm N xuống rất nhỏ (vd 4) để tự tay thấy pitfall overhead GPU verified ở trên:
Tóm lược
- ✅ SIMD "nới rộng" một lệnh CPU để xử lý nhiều dữ liệu cùng lúc; SIMT (GPU) chạy hàng nghìn luồng thật sự độc lập — khác nhau về bản chất tổ chức song song.
- ✅ NPU/Tensor Core và Apple AMX là phần cứng CHUYÊN DỤNG chỉ để tăng tốc nhân ma trận.
- ✅ Verified: nhân ma trận 1024×1024 = 2.146.435.072 FLOPs. SIMD nhanh hơn scalar đúng 8 lần (= độ rộng vector); GPU nhanh hơn scalar 2500 lần.
- ✅ Pitfall: với ma trận quá nhỏ (4×4), overhead nạp dữ liệu vào GPU khiến nó CHẬM HƠN CPU vô hướng — verified thật bằng engine.
- ✅ Pitfall: GPU rất yếu với code rẽ nhánh tuần tự phức tạp — không thể thay thế CPU cho mọi việc.
- ✅ Roofline: thông lượng đạt được = $\min(\text{FLOPS}_{\text{đỉnh}}, \text{AI} \times \text{băng thông})$. Verified trên CÙNG một GPU 10 TFLOPS / 600 GB/s: nhân ma trận (AI 170) dùng 100% công suất, cộng vector (AI 0,167) chỉ 1% — FLOPS đỉnh không đủ để đoán hiệu năng.
- ✅ Pitfall: với bài toán nghẽn băng thông, mua phần cứng nhiều FLOPS hơn cho đúng 0% cải thiện. Chia khối để tái dùng cache (Bài 7) mới là thứ kéo AI sang phía nghẽn tính toán.
- ✅ Phân kỳ warp: verified, chỉ MỘT luồng trong 32 đi khác hướng đã kéo hiệu suất cả warp xuống 50% — đúng bằng thiệt hại của warp chia đôi 16/16. Thiệt hại không tỷ lệ với số luồng lạc loài.
Cả bài này đi theo một hướng: muốn nhanh hơn thì đắp thêm phần cứng chuyên dụng — nhiều nhân hơn, khối ma trận riêng, băng thông rộng hơn. Nhưng "đắp thêm" nghĩa là con chip to ra, mà chip càng to thì xác suất dính một hạt bụi làm hỏng nó càng cao, và giá thành tăng nhanh hơn hẳn diện tích. Đó là bức tường kinh tế đứng sau mọi bức tường kỹ thuật ở trên — Bài 11 sẽ tính đúng cái giá đó, và cho thấy vì sao ngành công nghiệp buộc phải cắt con chip lớn thành nhiều mảnh nhỏ (chiplet).
Trắc nghiệm ôn tập
Câu 1
Khác biệt CỐT LÕI giữa SIMD (CPU vector) và SIMT (GPU) là gì?
Câu 2
Verified: nhân ma trận 1024×1024 trên SIMD (32 GFLOPS) nhanh hơn scalar (4 GFLOPS) đúng 8 lần. Con số 8 lần này đến từ đâu?
Câu 3
Verified: với ma trận 4×4 (chỉ 112 FLOPs), GPU (có overhead nạp dữ liệu 0,1ms) lại CHẬM HƠN CPU vô hướng — dù GPU "mạnh hơn" hẳn về lý thuyết. Vì sao?
Câu 4
Vì sao lượng tử hoá (quantization) FP32→INT8 cần được đo đạc cẩn thận thay vì áp dụng đại trà?
Tải file code thực hành minh họa bài học
File JavaScript CPUJS — thư viện kiến trúc máy tính mini dùng xuyên suốt cả 12 bài, Bài 10
vừa thêm matrixMultiplyFlops(), computeTimeSeconds(),
compareComputeMethods(), rooflineAttainable(),
matmulArithmeticIntensity(), vectorAddArithmeticIntensity(),
warpDivergence() — đếm FLOPs nhân ma trận, so sánh thời gian tính giữa 3 kiến trúc, mô hình
Roofline (cường độ số học vs băng thông) và phân kỳ warp, kèm self-test đối chiếu đúng mọi con số trong
bài (chạy node cpu-core.js, không cần cài thêm gì):
📖 Tài liệu tham khảo
- Tổng quan kiến trúc SIMD/SIMT: Wikipedia — SIMD — bao gồm cả phần so sánh với SIMT (Single Instruction, Multiple Threads) trên GPU.
- Apple AMX & Accelerate: Apple Developer — Accelerate framework — tài liệu chính thức về các thư viện tăng tốc phần cứng trên Apple Silicon.
- Lượng tử hoá mô hình AI: Wikipedia — Quantization — nguyên lý lượng tử hoá tín hiệu áp dụng cho trọng số mạng thần kinh nhân tạo.
Bình luận