I/O, Lưu trữ & Tài nguyên
17/26
Bài 17 / 26~14 phútĐường đi I/O & GPUMiễn phí… lượt xem

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áyTrê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ànLiên kết PCIe
Công đoạn chế biến sâuBà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 xeBatching

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.

Hai bang thong ve dung ti le, roi hai ca dang va khong dang chuyen sang device

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.

Thử ngẫmsếp muốn đưa phép cộng hai vector triệu phần tử lên GPU để tận dụng phần cứng mạnh. Tỉ lệ phép tính trên byte chuyển ở đây thấp hay cao, và điều đó nói gì về quyết định này?

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.

Gợi ý định hướng

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)

📚 Nguồn kỹ thuật
  • 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

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Ca 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?
  2. Q2
    Ca 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?
  3. Q3
    Ca 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?
  4. Q4
    Vì 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?
  5. Q5
    Vì 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ì?

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

Đặ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

Tổng kết module — Đường đi I/O & GPU