Host và device — vì sao chép dữ liệu đắt hơn tính
GPU không dùng chung RAM với CPU. Bài này đo cái giá đó: PCIe, pinned memory, batching, và vì sao nhiều pipeline AI nghẽn ở đường truyền chứ không ở phép tính.
TL;DR: GPU có bộ nhớ riêng, và giữa nó với RAM của CPU là một sợi dây PCIe hẹp hơn hẳn. Tài liệu của NVIDIA đặt hai con số cạnh nhau rất thẳng thắn: băng thông giữa GPU và bộ nhớ thiết bị cao hơn nhiều lần băng thông giữa bộ nhớ host và bộ nhớ device. Hệ quả là một phép tính rẻ trên khối dữ liệu lớn thường không đáng để chuyển sang GPU — thời gian chép át hết thời gian tính. Câu hỏi quyết định không phải "GPU tính nhanh hơn bao nhiêu lần" mà là bao nhiêu phép tính trên mỗi byte phải chuyển.
Một team đo lại pipeline suy luận của họ: kernel GPU chạy 4 ms, nhưng mỗi request vẫn mất 40 ms. Thêm GPU thứ hai không giúp gì. Profiler chỉ ra 90% thời gian nằm ở hai thanh Memcpy HtoD và Memcpy DtoH.
Đây là bài đóng module, và cũng là bài buộc bạn tự chẩn đoán. Mọi thứ cần thiết đã có: bảng băng thông PCIe ở bài 01, DMA ở bài 02, cách đếm bản sao ở bài 05, kiến trúc GPU ở bài 06.
1. Analogy — nhà máy khổng lồ ở cuối con đường một làn
Bạn xây một nhà máy có năng suất gấp trăm lần xưởng cũ. Nguyên liệu thì vẫn nằm ở kho bên kia thành phố, và con đường nối hai nơi chỉ có một làn xe.
Nếu mỗi chuyến hàng được chế biến sâu — vào một tấn nguyên liệu, ra vài kilogram thành phẩm sau nhiều công đoạn — thì nhà máy đáng đồng tiền. Nhưng nếu công đoạn duy nhất là dán nhãn, thì xe tải chạy đi chạy về suốt ngày trong khi dây chuyền đứng chờ. Nhà máy không có lỗi gì cả; sai là ở chỗ giao cho nó loại việc mà chi phí vận chuyển át chi phí gia công.
| Ở nhà máy | Trên máy |
|---|---|
| Kho nguyên liệu bên kia thành phố | RAM của host |
| Nhà máy và bãi chứa tại chỗ | GPU và bộ nhớ device |
| Con đường một làn | Liên kết PCIe |
| Công đoạn chế biến sâu | Bài toán nhiều phép tính trên mỗi byte |
| Chỉ dán nhãn rồi trả về | Phép tính rẻ trên khối dữ liệu lớn |
| Gom đủ chuyến mới chạy xe | Batching |
2. Vì sao cái giá của một lần chép lại lớn đến thế?
GPU rời không dùng chung bộ nhớ với CPU. Nó có bộ nhớ riêng gắn ngay trên bo mạch, và mọi dữ liệu muốn được tính đều phải chuyển qua PCIe trước — chính là cây thiết bị ở bài 01, và chuyển bằng DMA đúng như bài 02 mô tả.
Chỗ đau nằm ở chênh lệch giữa hai băng thông. Tài liệu best practices của NVIDIA nói thẳng rằng băng thông lý thuyết giữa bộ nhớ device và GPU cao hơn nhiều lần băng thông lý thuyết giữa bộ nhớ host và bộ nhớ device; ví dụ tài liệu đưa ra là 898 GB/s trên Tesla V100 so với 16 GB/s của PCIe x16 Gen3. Hai con số đó gắn với đời phần cứng cụ thể, nhưng tỉ lệ thì vẫn đúng qua các thế hệ: GPU đọc bộ nhớ của chính nó nhanh hơn hẳn so với lấy dữ liệu từ phía host.

Bảng băng thông PCIe ở bài 01 cho bạn trần lý thuyết của từng thế hệ, cứ thế mà tính. Một khe PCIe 4.0 x16 cho khoảng 31,5 GB/s lý thuyết, thực đo thấp hơn — chuyển 8 GB sang GPU tốn ít nhất vài trăm mili-giây, và ngần ấy thời gian không có phép tính nào diễn ra.
Từ đó ra chỉ số cần nhìn: số phép tính trên mỗi byte phải chuyển. Tài liệu NVIDIA lấy đúng ví dụ này — chuyển hai ma trận sang device để cộng rồi chuyển kết quả về thì gần như không thu được lợi ích, vì tỉ lệ giữa số phép tính và số phần tử phải chuyển chỉ là một trên ba. So với nó, nhân hai ma trận kích thước n cần cỡ n³ phép tính trên n² phần tử, nên càng lớn càng đáng chuyển.
3. Ba đòn bẩy khi khâu chuyển là nút thắt
Thứ nhất: pinned memory. Tài liệu NVIDIA ghi rằng bộ nhớ page-locked (pinned) đạt băng thông cao nhất giữa host và device. Lý do là trang thông thường có thể bị kernel hoán đổi hoặc di dời bất cứ lúc nào, nên DMA engine không được nhắm thẳng vào chúng — driver phải trung chuyển qua một vùng pinned nội bộ, tức thêm một lần chép do CPU làm. Đổi lại, trang pinned không page out được nên giữ cứng RAM vật lý; ghim tràn lan là đẩy cả máy vào cảnh thiếu RAM.
Thứ hai: chồng lấn chép với tính. Chia dữ liệu thành từng khối rồi cho việc chép khối sau chạy đồng thời với việc tính khối trước. Phần cứng cho phép vì DMA engine hoạt động độc lập với đơn vị tính toán, đúng tính chất mà bài 02 đã dựng. Làm tốt thì thời gian chép gần như biến mất sau thời gian tính, thay vì cộng dồn vào.
Thứ ba, và thường mạnh nhất: đừng chuyển. Dữ liệu phải qua năm bước xử lý thì giữ nó nằm lại bộ nhớ device suốt cả năm bước, thay vì đưa về host sau mỗi bước. Nhiều pipeline chậm không vì một lần chuyển đắt, mà vì chúng chuyển đi chuyển lại chục lần cho một luồng dữ liệu.
4. Tự chẩn đoán — ba pipeline, ba kết luận
Đây là phần bạn tự làm. Với mỗi ca dưới đây, xác định nút thắt nằm ở đâu, và nói rõ con số nào trong đề bài chứng minh điều đó. Viết chẩn đoán của bạn ra giấy trước khi mở phần tự kiểm tra ở cuối bài — đáp án nằm trong đó.
Ca A. Service resize ảnh: mỗi ảnh 12 MB, chuyển sang GPU, chạy một kernel đổi kích thước, chuyển kết quả 3 MB về. Kernel đo được 0,8 ms. Thông lượng dừng ở khoảng 60 ảnh mỗi giây trên khe PCIe 3.0 x16, và thêm GPU thứ hai không cải thiện gì.
Ca B. Huấn luyện mô hình: mỗi bước nạp một batch 200 MB từ RAM host, tính 180 ms trên GPU, không chuyển gì về giữa các bước. GPU đạt khoảng 85% mức sử dụng. Team muốn mua pinned memory và tăng kích thước batch để "giảm chi phí chuyển".
Ca C. Pipeline xử lý tín hiệu bảy giai đoạn. Mỗi giai đoạn là một kernel GPU, và giữa hai giai đoạn liên tiếp, kết quả được chuyển về host để ghi log rồi chuyển ngược lên. Tổng thời gian bảy kernel là 15 ms, tổng thời gian mỗi request là 140 ms.
Không có kết luận nào ở đây, chỉ là ba câu hỏi để bạn tự trả lời cho từng ca. Thứ nhất: mỗi request chuyển bao nhiêu byte qua PCIe, và với trần lý thuyết của thế hệ khe cắm đó thì riêng việc chuyển tốn tối thiểu bao lâu? Thứ hai: đặt con số vừa tính cạnh thời gian kernel — cái nào lớn hơn, và lớn hơn mấy lần? Thứ ba: trong ba đòn bẩy ở mục 3, đòn nào tác động vào đúng đại lượng đang chiếm phần lớn thời gian của ca này?
5. Pitfall của riêng concept này
❌ Nhầm 1 — đo tốc độ kernel rồi kết luận về tốc độ pipeline.
✅ Phải đo trọn vòng: chép sang, tính, chép về. Một kernel 4 ms nằm trong pipeline 40 ms nghĩa là 90% thời gian ở chỗ khác. Bỏ hai đầu chép ra khỏi phép đo cho ra con số đẹp mà vô nghĩa.
❌ Nhầm 2 — thêm GPU khi nút thắt nằm ở PCIe.
✅ Bus có trần cứng, và thêm thiết bị chỉ tăng tranh chấp ở root complex. Đây đúng bài học của CPU-bound so với I/O-bound, chỉ đổi tầng: thêm worker không cứu được nút thắt nằm ở tầng dưới.
❌ Nhầm 3 — ghim pinned memory tràn lan.
✅ Trang pinned không page out được nên giữ cứng RAM vật lý. Ghim quá nhiều là đẩy cả máy vào cảnh thiếu RAM và swap, mất nhiều hơn phần băng thông thu được. Chỉ ghim buffer đang trong pipeline.
❌ Nhầm 4 — tối ưu từng lần chuyển mà không hỏi vì sao phải chuyển nhiều lần thế.
✅ Trước khi tinh chỉnh cách chép, hãy đếm số lần dữ liệu qua lại giữa host và device trong một request.
6. 📚 Đào sâu (tuỳ chọn)
- CUDA C++ Best Practices Guide — mục về chuyển dữ liệu host-device: so sánh băng thông bộ nhớ device với băng thông PCIe, khuyến nghị pinned memory, và ví dụ cộng ma trận cho thấy tỉ lệ phép tính trên phần tử phải chuyển quyết định việc dùng GPU có đáng hay không.
- Nsight Compute — Kernel Profiling Guide — các chỉ số cần nhìn khi muốn biết kernel đang nghẽn ở tính toán hay ở bộ nhớ.
7. Liên hệ các bài khác
- Thiết bị, MMIO và PCIe — bảng băng thông PCIe dùng làm trần lý thuyết cho mọi phép tính trong bài này.
- Polling, interrupt và DMA — DMA engine chạy độc lập với đơn vị tính toán, tính chất khiến chồng lấn chép với tính khả thi.
- Zero-copy — cùng nguyên lý "bản sao thừa tốn tiền thật", ở đường file ra socket.
- GPU về mặt kiến trúc — vì sao GPU tính nhanh; bài này giải thích vì sao tính nhanh vẫn chưa đủ.
- Tổng kết module — cheat sheet và self-assessment cho cả bảy bài.
8. Tóm tắt
- Chỉ số quyết định là số phép tính trên mỗi byte phải chuyển; cộng hai ma trận thì không đáng, nhân hai ma trận thì càng lớn càng đáng.
- Pinned memory bỏ được bước trung chuyển trước khi DMA, nhưng ghim tràn lan thì giữ cứng RAM vật lý và phản tác dụng.
- Chồng lấn chép với tính giấu được thời gian chuyển sau thời gian tính, vì DMA engine chạy độc lập với đơn vị tính toán.
- Đòn mạnh nhất thường là giảm số lần chuyển: giữ dữ liệu nằm lại device qua nhiều giai đoạn thay vì đưa về host sau mỗi bước.
9. Tự kiểm tra
Q1Ca A — resize ảnh: 12 MB lên, 3 MB về, kernel 0,8 ms, dừng ở khoảng 60 ảnh mỗi giây trên PCIe 3.0 x16, thêm GPU không giúp. Nút thắt nằm ở đâu, và con số nào chứng minh?▸
Nút thắt là PCIe. Mỗi ảnh chuyển 15 MB qua bus; với trần lý thuyết khoảng 15,8 GB/s của PCIe 3.0 x16, riêng phần chuyển đã tốn cỡ 1 ms mỗi ảnh trong điều kiện lý tưởng, còn thực đo thì hơn. Đặt cạnh kernel 0,8 ms, thời gian chép đã ngang hoặc vượt thời gian tính — và ở mức 60 ảnh mỗi giây thì bus đang tải khoảng 0,9 GB/s hữu ích, chưa kể chi phí cố định mỗi lần chuyển.
Chi tiết "thêm GPU thứ hai không cải thiện" là bằng chứng chốt: nếu nghẽn ở tính toán thì GPU thứ hai phải giúp. Đòn bẩy đúng ở đây là gom nhiều ảnh thành một lần chuyển và chồng lấn chép với tính, hoặc xét lại xem một phép resize đơn giản có đáng đưa lên GPU không.
Q2Ca B — huấn luyện: batch 200 MB, tính 180 ms, GPU dùng 85%, không chuyển gì về giữa các bước. Team muốn dùng pinned memory và tăng batch để 'giảm chi phí chuyển'. Chẩn đoán của bạn?▸
Ca này không nghẽn ở khâu chuyển. 200 MB qua một khe PCIe hiện đại tốn cỡ vài chục mili-giây hoặc ít hơn, so với 180 ms tính toán — tức khâu chuyển chiếm phần nhỏ, và mức sử dụng GPU 85% khẳng định điều đó. Nghẽn ở khâu chuyển thì con số ấy phải thấp hơn nhiều.
Pinned memory và batch lớn hơn vẫn có thể nhặt được vài phần trăm, nhưng đó là tối ưu vào đại lượng không chiếm phần lớn thời gian. Muốn cải thiện thật thì phải nhìn vào chính kernel: nó đang nghẽn ở băng thông bộ nhớ device hay ở đơn vị tính toán, và truy cập bộ nhớ đã gộp được chưa.
Q3Ca C — bảy giai đoạn, mỗi giai đoạn chuyển kết quả về host để ghi log rồi chuyển ngược lên; bảy kernel tổng 15 ms nhưng mỗi request mất 140 ms. Vấn đề là gì, và sửa thế nào?▸
Kiến trúc bắt dữ liệu qua lại PCIe mười bốn lần cho một request. Bảy kernel chỉ chiếm 15 ms trong 140 ms, nghĩa là khoảng 89% thời gian nằm ở chuyển dữ liệu và các lần đồng bộ kèm theo. Đây không phải chuyện tinh chỉnh cách chép, mà là chuyện thiết kế luồng.
Sửa bằng cách giữ dữ liệu nằm lại bộ nhớ device suốt cả bảy giai đoạn và chỉ chuyển kết quả cuối về. Việc ghi log có thể lấy số liệu tóm tắt nhỏ thay vì cả khối dữ liệu, hoặc gom lại làm một lần ở cuối. Đây là đòn bẩy thứ ba của mục 3, và trong ba đòn bẩy thì nó thường thắng đậm nhất.
Q4Vì sao pinned memory làm việc chuyển nhanh hơn, và vì sao ghim tràn lan lại phản tác dụng?▸
Trang bộ nhớ thông thường có thể bị kernel hoán đổi hoặc di dời bất cứ lúc nào, nên DMA engine không được nhắm thẳng vào chúng: driver phải trung chuyển qua một vùng pinned nội bộ, tức thêm một lần chép do CPU làm. Ghim trang lại thì bỏ được bước ấy, và tài liệu NVIDIA ghi rằng bộ nhớ pinned đạt băng thông cao nhất giữa host và device. Phản tác dụng vì trang pinned không page out được nên giữ cứng RAM vật lý — ghim quá nhiều đẩy cả máy vào cảnh thiếu RAM và swap, mất nhiều hơn phần băng thông thu được.
Q5Vì sao 'GPU nhanh hơn CPU bao nhiêu lần' là câu hỏi sai khi quyết định có đưa một phép tính lên GPU hay không? Câu hỏi đúng là gì?▸
Vì tốc độ tính thô không nói gì về chi phí chuyển dữ liệu đi và về: một phép tính rẻ trên khối dữ liệu lớn vẫn thua dù GPU nhanh gấp trăm lần, đơn giản vì thời gian chép át thời gian tính. Câu hỏi đúng là số phép tính thực hiện được trên mỗi byte phải chuyển. Tài liệu NVIDIA lấy ví dụ cộng hai ma trận — tỉ lệ chỉ là một trên ba nên gần như không thu được lợi ích; ngược lại nhân hai ma trận cần cỡ n³ phép tính trên n² phần tử, nên càng lớn càng đáng chuyển.
Bài tiếp theo: Tổng kết module — Đường đi I/O & GPU
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
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