.. raw:: html
.. _sec_hardware:
Phần cứng
=========
.. raw:: html
Để xây dựng các hệ thống có hiệu năng cao, ta cần nắm chắc kiến thức về
các thuật toán và mô hình để có thể biểu diễn được những khía cạnh thống
kê của bài toán. Đồng thời, ta cũng cần có một chút kiến thức cơ bản về
phần cứng thực thi ở bên dưới. Nội dung trong phần này không thể thay
thế một khóa học đầy đủ về phần cứng và thiết kế hệ thống, mà sẽ chỉ
đóng vai trò như điểm bắt đầu để giúp người đọc hiểu tại sao một số
thuật toán lại hiệu quả hơn các thuật toán khác và làm thế nào để đạt
được thông lượng cao. Thiết kế tốt có thể dễ dàng tạo ra sự khác biệt
rất lớn, giữa việc có thể huấn luyện một mô hình (ví dụ trong khoảng một
tuần) và không thể huấn luyện (ví dụ mất 3 tháng để huấn luyện xong, từ
đó không kịp tiến độ). Ta sẽ bắt đầu bằng việc quan sát tổng thể một hệ
thống máy tính. Tiếp theo, ta sẽ đi sâu hơn và xem xét chi tiết về CPU
và GPU. Cuối cùng, ta sẽ tìm hiểu cách các máy tính được kết nối với
nhau trong trạm máy chủ hay trên đám mây. Cần lưu ý, phần này sẽ không
hướng dẫn cách lựa chọn card GPU. Nếu bạn cần gợi ý, hãy xem
:numref:`sec_buy_gpu`. Phần giới thiệu về điện toán đám mây trên AWS
có thể tìm thấy tại :numref:`sec_aws`.
.. raw:: html
Bạn đọc có thể tham khảo nhanh thông tin tóm tắt trong
:numref:`fig_latencynumbers`. Nội dung này được trích dẫn từ bài viết
của `Colin
Scott `__
trình bày tổng quan về những tiến bộ trong thập kỉ qua. Số liệu gốc được
trích dẫn từ buổi thảo luận của Jeff Dean tại `trường Stanford năm
2010 `__.
Phần thảo luận dưới đây sẽ giải thích cơ sở cho những con số trên và
cách mà chúng dẫn dắt ta trong quá trình thiết kế thuật toán. Nội dung
khái quát và ngắn gọn nên nó không thể thay thế một khóa học đầy đủ,
nhưng sẽ cung cấp đủ thông tin cho những người làm mô hình thống kê để
có thể đưa ra lựa chọn thiết kế phù hợp. Để có cái nhìn tổng quan chuyên
sâu về kiến trúc máy tính, bạn đọc có thể tham khảo
:cite:`Hennessy.Patterson.2011` hay một khóa học gần đây của `Arste
Asanovic `__.
.. raw:: html
.. _fig_latencynumbers:
.. figure:: ../img/latencynumbers.png
Số liệu về độ trễ mà mọi lập trình viên nên biết.
.. raw:: html
Máy tính
--------
.. raw:: html
Hầu hết những nhà nghiên cứu học sâu đều được trang bị hệ thống máy tính
có bộ nhớ và khả năng tính toán khá lớn với một hay nhiều GPU. Những máy
tính này thường có những thành phần chính sau:
.. raw:: html
- Bộ xử lý, thường được gọi là CPU, có khả năng thực thi các chương
trình được nhập bởi người dùng (bên cạnh chức năng chạy hệ điều hành
và các tác vụ khác), thường có 8 lõi (*core*) hoặc nhiều hơn.
- Bộ nhớ (RAM) được sử dụng để lưu trữ và truy xuất các kết quả tính
toán như vector trọng số, giá trị kích hoạt và dữ liệu huấn luyện.
- Một hay nhiều kết nối Ethernet với tốc độ đường truyền từ 1 Gbit/s
tới 100 Gbit/s (các máy chủ tân tiến còn có các phương thức kết nối
cao cấp hơn nữa).
- Cổng giao tiếp bus mở rộng tốc độ cao (PCIe) kết nối hệ thống với một
hay nhiều GPU. Các hệ thống máy chủ thường có tới 8 GPU được kết nối
với nhau theo cấu trúc liên kết phức tạp. Còn các hệ thống máy tính
thông thường thì có 1-2 GPU, phụ thuộc vào túi tiền của người dùng và
công suất nguồn điện.
- Bộ lưu trữ tốt, thường là ổ cứng từ (HDD) hay ổ cứng thể rắn (SSD),
được kết nối bằng bus PCIe giúp truyền dữ liệu huấn luyện tới hệ
thống và sao lưu các checkpoint trung gian một cách hiệu quả.
.. raw:: html
.. _fig_mobo-symbol:
.. figure:: ../img/mobo-symbol.svg
Kết nối các thành phần máy tính
.. raw:: html
Hình :numref:`fig_mobo-symbol` cho thấy, hầu hết các thành phần (mạng,
GPU, ổ lưu trữ) được kết nối tới GPU thông qua đường bus PCI mở rộng.
Đường truyền này gồm nhiều làn kết nối trực tiếp tới CPU. Ví dụ,
Threadripper 3 của AMD có 64 làn PCIe 4.0, mỗi làn có khả năng truyền
dẫn 16 Gbit/s dữ liệu theo cả hai chiều. Bộ nhớ được kết nối trực tiếp
tới CPU với tổng băng thông lên đến 100 GB/s.
.. raw:: html
Khi ta chạy chương trình trên máy tính, ta cần trộn dữ liệu ở các bộ xử
lý (CPU hay GPU), thực hiện tính toán và sau đó truyền kết quả tới RAM
hay ổ lưu trữ. Do đó, để có hiệu năng tốt, ta cần đảm bảo rằng chương
trình chạy mượt mà và hệ thống không có nút nghẽn cổ chai. Ví dụ, nếu ta
không thể tải ảnh đủ nhanh, bộ xử lý sẽ không có có dữ liệu để chạy.
Tương tự, nếu ta không thể truyền các ma trận tới CPU (hay GPU) đủ
nhanh, bộ xử lý sẽ thiếu dữ liệu để hoạt động. Cuối cùng, nếu ta muốn
đồng bộ nhiều máy tính trong một mạng, kết nối mạng không nên làm chậm
việc tính toán. Xen kẽ việc giao tiếp và tính toán giữa các máy tính là
một phương án cho vấn đề này. Giờ hãy xem xét các thành phần trên một
cách chi tiết hơn.
.. raw:: html
Bộ nhớ
------
.. raw:: html
Về cơ bản, bộ nhớ được sử dụng để lưu trữ dữ liệu khi cần sẵn sàng truy
cập. Hiện tại bộ nhớ RAM của CPU thường thuộc loại
`DDR4 `__, trong đó mỗi mô-đun
có băng thông 20-25GB/s và độ rộng bus 64 bit. Thông thường, các cặp
mô-đun bộ nhớ cho phép sử dụng đa kênh. CPU có từ 2 đến 4 kênh bộ nhớ,
nghĩa là chúng có băng thông bộ nhớ tối đa từ 40 GB/s đến 100 GB/s.
Thường thì mỗi kênh có hai dải (*bank*). Ví dụ, Zen 3 Threadripper của
AMD có 8 khe cắm.
.. raw:: html
Dù những con số trên trông khá ấn tượng, trên thực tế chúng chỉ nói lên
một phần nào đó. Khi muốn đọc một phần nào đó từ bộ nhớ, trước tiên ta
cần chỉ cho mô-đun bộ nhớ vị trí chứa thông tin, tức cần gửi *địa chỉ*
đến RAM. Khi thực hiện xong việc này, ta có thể chọn chỉ đọc một bản ghi
64 bit hoặc một chuỗi dài các bản ghi. Lựa chọn thứ hai được gọi là *đọc
nhanh* (*burst read*). Nói ngắn gọn, việc gửi một địa chỉ vào bộ nhớ và
thiết lập chuyển tiếp sẽ mất khoảng 100ns (thời gian cụ thể phụ thuộc
vào hệ số thời gian của từng chip bộ nhớ được sử dụng), mỗi lần chuyển
tiếp sau đó chỉ mất 0.2ns. Có thể thấy lần đọc đầu tiên tốn thời gian
gấp 500 lần những lần sau! Ta có thể đọc ngẫu nhiên tối đa
:math:`10,000,000` lần mỗi giây. Điều này cho thấy rằng ta nên hạn chế
tối đa việc truy cập bộ nhớ ngẫu nhiên và thay vào đó nên sử dụng cách
đọc (và ghi) nhanh (*burst read*, và *burst write*).
.. raw:: html
Mọi thứ trở nên phức tạp hơn một chút khi ta tính đến việc có nhiều dải
bộ nhớ. Mỗi dải có thể đọc bộ nhớ gần như là độc lập với nhau. Điều này
có hai ý sau. Thứ nhất, số lần đọc ngẫu nhiên thực sự cao hơn tới 4 lần,
miễn là chúng được trải đều trên bộ nhớ. Điều đó cũng có nghĩa là việc
thực hiện các lệnh đọc ngẫu nhiên vẫn không phải là một ý hay vì các
lệnh đọc nhanh (*burst read*) cũng nhanh hơn gấp 4 lần. Thứ hai, do việc
căn chỉnh bộ nhớ theo biên 64 bit, ta nên căn chỉnh mọi cấu trúc dữ liệu
theo cùng biên đó. Trình biên dịch thực hiện việc này một cách `tự
động `__ khi các
cờ thích hợp được đặt. Độc giả có thể tham khảo thêm bài giảng về DRAM
ví dụ như `Zeshan
Chishti `__.
.. raw:: html
Bộ nhớ GPU còn yêu cầu băng thông cao hơn nữa vì chúng có nhiều phần tử
xử lý hơn CPU. Nhìn chung có hai phương án tiếp cận đối với vấn đề này.
Một cách là mở rộng bus bộ nhớ. Chẳng hạn NVIDIA’s RTX 2080 Ti dùng bus
có kích thước 352 bit. Điều này cho phép truyền đi lượng thông tin lớn
hơn cùng lúc. Một cách khác là sử dụng loại bộ nhớ chuyên biệt có hiệu
năng cao cho GPU. Các thiết bị hạng phổ thông, điển hình như dòng RTX và
Titan của NVIDIA, dùng các chip
`GDDR6 `__ với băng thông
tổng hợp hơn 500 GB/s. Một loại bộ nhớ chuyên biệt khác là mô-đun HBM
(bộ nhớ băng thông rộng). Chúng dùng phương thức giao tiếp rất khác và
kết nối trực tiếp với GPU trên một tấm bán dẫn silic chuyên biệt. Điều
này dẫn đến giá thành rất cao và chúng chỉ được sử dụng chủ yếu cho các
chip máy chủ cao cấp, ví dụ như dòng GPU NVIDIA Volta V100. Không quá
ngạc nhiên, kích thước bộ nhớ GPU nhỏ hơn nhiều so với bộ nhớ CPU do giá
thành cao của nó. Nhìn chung các đặc tính hiệu năng của bộ nhớ GPU khá
giống bộ nhớ CPU, nhưng nhanh hơn nhiều. Ta có thể bỏ qua các chi tiết
sâu hơn trong cuốn sách này, do chúng chỉ quan trọng khi cần điều chỉnh
các hạt nhân GPU để đạt thông lượng xử lý cao hơn.
.. raw:: html
Lưu trữ
-------
.. raw:: html
Chúng ta đã thấy đặc tính then chốt của RAM chính là *băng thông* và *độ
trễ*. Điều này cũng đúng đối với các thiết bị lưu trữ, sự khác biệt chỉ
có thể là các đặc tính trên lớn hơn nhiều lần.
.. raw:: html
**Các ổ cứng** đã được sử dụng hơn nửa thế kỷ. Một cách ngắn gọn, chúng
chứa một số đĩa quay với những đầu kim có thể di chuyển để đọc/ghi ở bất
cứ rãnh nào. Các ổ đĩa cao cấp có thể lưu trữ lên tới 16 TB trên 9 đĩa.
Một trong những lợi ích chính của ổ đĩa cứng là chúng tương đối rẻ.
Nhược điểm của chúng là độ trễ tương đối cao khi đọc dữ liệu và hay bị
hư hỏng nặng dẫn đến không thể đọc dữ liệu, thậm chí là mất dữ liệu.
.. raw:: html
Để hiểu về nhược điểm thứ hai, hãy xem xét thực tế rằng ổ cứng quay với
tốc độ khoảng 7,200 vòng/phút. Nếu tốc độ này cao hơn, các đĩa sẽ vỡ tan
do tác dụng của lực ly tâm. Điều này dẫn đến một nhược điểm lớn khi truy
cập vào một khu vực cụ thể trên đĩa: chúng ta cần đợi cho đến khi đĩa
quay đúng vị trí (chúng ta có thể di chuyển đầu kim nhưng không được
tăng tốc các đĩa). Do đó, có thể mất hơn 8ms cho đến khi truy cập được
dữ liệu yêu cầu. Vì thế mà ta hay nói ổ cứng có thể hoạt động ở mức xấp
xỉ 100 IOP. Con số này về cơ bản vẫn không thay đổi trong hai thập kỷ
qua. Tệ hơn nữa, việc tăng băng thông cũng khó khăn không kém (ở mức độ
100-200 MB/s). Rốt cuộc, mỗi đầu đọc một rãnh bit, do đó tốc độ bit chỉ
tăng theo tỷ lệ căn bậc hai của mật độ thông tin. Kết quả là các ổ cứng
đang nhanh chóng biến thành nơi lưu trữ cấp thấp cho các bộ dữ liệu rất
lớn.
.. raw:: html
**Ổ cứng thể rắn (SSD)** sử dụng bộ nhớ Flash để liên tục lưu trữ thông
tin. Điều này cho phép truy cập *nhanh hơn nhiều* vào các bản ghi đã
được lưu trữ. SSD hiện đại có thể hoạt động ở mức 100,000 đến 500,000
IOP, tức là nhanh hơn gấp 1000 lần so với ổ cứng HDD. Hơn nữa, băng
thông của chúng có thể đạt tới 1-3GB/s nghĩa là nhanh hơn 10 lần so với
ổ cứng. Những cải tiến này nghe có vẻ tốt đến mức khó tin. Thật vậy, và
SSD cũng đi kèm với một số hạn chế do cách mà chúng được thiết kế.
.. raw:: html
- Các ổ SSD lưu trữ thông tin theo khối (256 KB trở lên). Ta sẽ phải
ghi cả khối cùng một lúc, mất thêm thời gian đáng kể. Do đó việc ghi
ngẫu nhiên theo bit trên SSD có hiệu suất rất tệ. Tương tự như vậy,
việc ghi dữ liệu nói chung mất thời gian đáng kể vì khối phải được
đọc, xóa và sau đó viết lại với thông tin mới. Cho đến nay, bộ điều
khiển và firmware của SSD đã phát triển các thuật toán để giảm thiểu
vấn đề này. Tuy nhiên tốc độ ghi vẫn có thể chậm hơn nhiều, đặc biệt
là đối với SSD QLC (ô bốn cấp). Chìa khóa để cải thiện hiệu suất là
đưa các thao tác vào một *hàng đợi* để ưu tiên việc đọc trước và chỉ
ghi theo các khối lớn nếu có thể.
- Các ô nhớ trong SSD bị hao mòn tương đối nhanh (thường sau vài nghìn
lần ghi). Các thuật toán bảo vệ mức hao mòn có thể phân bổ đều sự
xuống cấp trên nhiều ô. Dù vậy, vẫn không nên sử dụng SSD cho các tệp
hoán đổi (*swap file*) hoặc cho tập hợp lớn các tệp nhật ký (*log
file*).
- Cuối cùng, sự gia tăng lớn về băng thông đã buộc các nhà thiết kế máy
tính phải gắn SSD trực tiếp vào bus PCIe. Các ổ đĩa có khả năng xử lý
việc này, được gọi là NVMe (Bộ nhớ không biến động tăng cường - *Non
Volatile Memory enhanced*), có thể sử dụng lên tới 4 làn PCIe. Băng
thông có thể lên tới 8GB/s trên PCIe 4.0.
.. raw:: html
**Lưu trữ đám mây** cung cấp nhiều lựa chọn hiệu suất có thể tùy chỉnh.
Nghĩa là, việc chỉ định bộ lưu trữ cho các máy ảo là tùy chỉnh, cả về số
lượng và tốc độ, do người dùng quyết định. Chúng tôi khuyên người dùng
nên tăng số lượng IOP được cung cấp bất cứ khi nào độ trễ quá cao, ví dụ
như trong quá trình huấn luyện với dữ liệu gồm nhiều bản ghi nhỏ.
.. raw:: html
CPU
---
.. raw:: html
| Bộ xử lý trung tâm (Central Processing Units - CPU) là trung tâm của
mọi máy tính (như ở phần trước, chúng tôi đã mô tả tổng quan về những
phần cứng quan trọng cho các mô hình học sâu hiệu quả). CPU gồm một số
thành tố quan trọng: lõi xử lý (*core*) với khả năng thực thi mã máy,
| bus kết nối các lõi (cấu trúc kết nối cụ thể có sự khác biệt lớn giữa
các mô hình xử lý, đời chip và nhà sản xuất) và bộ nhớ đệm (*cache*)
cho phép truy cập với băng thông cao hơn và độ trễ thấp hơn so với
việc đọc từ bộ nhớ chính. Cuối cùng, hầu hết CPU hiện đại chứa những
đơn vị xử lý vector để hỗ trợ tính toán đại số tuyến tính và tích chập
với tốc độ cao vì chúng khá phổ biến trong xử lý phương tiện và học
máy.
.. raw:: html
.. _fig_skylake:
.. figure:: ../img/skylake.svg
CPU lõi tứ của bộ xử lý Intel Skylake
.. raw:: html
:numref:`fig_skylake` minh hoạ bộ xử lý Intel Skylake với CPU lõi tứ.
Nó có một GPU tích hợp, bộ nhớ cache và phương tiện kết nối bốn lõi.
Thiết bị ngoại vi (Ethernet, WiFi, Bluetooth, bộ điều khiển SSD, USB,
v.v.) là một phần của chipset hoặc được đính kèm trực tiếp (PCIe) với
CPU.
.. raw:: html
Vi kiến trúc (Micro-architecture)
---------------------------------
.. raw:: html
Mỗi nhân xử lý bao gồm các thành phần rất tinh vi. Mặc dù chi tiết khác
nhau giữa đời chip và nhà sản xuất, chức năng cơ bản của chúng đã được
chuẩn hóa tương đối. Front-end tải các lệnh và dự đoán nhánh nào sẽ được
thực hiện (ví dụ: cho luồng điều khiển). Sau đó các lệnh được giải mã từ
mã nguồn hợp ngữ (*assembly code*) thành vi lệnh. Mã nguồn hợp ngữ
thường chưa phải là mã nguồn cấp thấp nhất mà bộ xử lý thực thi. Thay
vào đó, các lệnh phức tạp có thể được giải mã thành một tập hợp các phép
tính cấp thấp hơn. Tiếp đó chúng được xử lý bằng một lõi thực thi. Các
bộ xử lý đời mới thường có khả năng thực hiện đồng thời nhiều câu lệnh.
Ví dụ, lõi ARM Cortex A77 trong :numref:`fig_cortexa77` có thể thực
hiện lên đến 8 phép tính cùng một lúc.
.. raw:: html
.. _fig_cortexa77:
.. figure:: ../img/a77.svg
Tổng quan về vi kiến trúc ARM Cortex A77
.. raw:: html
Điều này có nghĩa là các chương trình hiệu quả có thể thực hiện nhiều
hơn một lệnh trên một chu kỳ xung nhịp, *giả sử* rằng chúng có thể được
thực hiện một cách độc lập. Không phải tất cả các bộ xử lý đều được tạo
ra như nhau. Một số được thiết kế chuyên biệt cho các lệnh về số nguyên,
trong khi một số khác được tối ưu hóa cho việc tính toán số thực dấu
phẩy động. Để tăng thông lượng, bộ xử lý cũng có thể theo đồng thời
nhiều nhánh trong một lệnh rẽ nhánh và sau đó loại bỏ các kết quả của
nhánh không được thực hiện. Đây là lý do vì sao đơn vị dự đoán nhánh có
vai trò quan trọng (trên front-end), bởi chúng chỉ chọn những nhánh có
khả năng cao được rẽ.
.. raw:: html
Vector hóa (Vectorization)
--------------------------
.. raw:: html
Học sâu đòi hỏi sức mạnh tính toán cực kỳ lớn. Vì vậy, CPU phù hợp với
học máy cần phải thực hiện được nhiều thao tác trong một chu kỳ xung
nhịp. Ta có thể đạt được điều này thông qua các đơn vị vector. Trên chip
ARM chúng được gọi là NEON, trên x86 thế hệ đơn vị vector mới nhất được
gọi là
`AVX2 `__. Một
khía cạnh chung là chúng có thể thực hiện SIMD (đơn lệnh đa dữ liệu -
*single instruction multiple data*). :numref:`fig_neon128` cho thấy
cách cộng 8 số nguyên ngắn trong một chu kỳ xung nhịp trên ARM.
.. raw:: html
.. _fig_neon128:
.. figure:: ../img/neon128.svg
Vector hóa NEON 128 bit
.. raw:: html
Phụ thuộc vào các lựa chọn kiến trúc, các thanh ghi như vậy có thể dài
tới 512 bit, cho phép tổ hợp tối đa 64 cặp số. Chẳng hạn, ta có thể nhân
hai số và cộng chúng với số thứ ba, cách này còn được biết đến như phép
nhân-cộng hợp nhất (*fused multiply-add*).
`OpenVino `__ của Intel sử dụng thao tác
này để đạt được thông lượng đáng nể cho học sâu trên CPU máy chủ. Tuy
nhiên, xin lưu ý rằng tốc độ này hoàn toàn không đáng kể so với khả năng
của GPU. Ví dụ, RTX 2080 Ti của NVIDIA có 4,352 nhân CUDA, mỗi nhân có
khả năng xử lý một phép tính như vậy tại bất cứ thời điểm nào.
.. raw:: html
Bộ nhớ đệm
----------
.. raw:: html
Xét tình huống sau: ta có một CPU bình thường với 4 nhân như trong
:numref:`fig_skylake` trên, hoạt động ở tần số 2 GHz. Thêm nữa, hãy
giả sử IPC (*instruction per clock* - số lệnh mỗi xung nhịp) là 1 và mỗi
nhân đều đã kích hoạt AVX2 rộng 256 bit. Ngoài ra, giả sử bộ nhớ cần
truy cập ít nhất một thanh ghi được sử dụng trong các lệnh AVX2. Điều
này có nghĩa CPU xử lý 4 x 256 bit = 1 kbit dữ liệu mỗi chu kỳ xung
nhịp. Trừ khi ta có thể truyền
:math:`2 \cdot 10^9 \cdot 128 = 256 \cdot 10^9` byte đến vi xử lý mỗi
giây, các nhân sẽ thiếu dữ liệu để xử lý. Tiếc thay giao diện bộ nhớ của
bộ vi xử lý như trên chỉ hỗ trợ tốc độ truyền dữ liệu khoảng 20-40 GB/s,
nghĩa là thấp hơn 10 lần. Để khắc phục vấn đề này, ta cần tránh nạp dữ
liệu *mới* từ bộ nhớ ngoài, và tốt hơn hết là lưu trong bộ nhớ cục bộ
trên CPU. Đây chính là lúc bộ nhớ đệm trở nên hữu ích (xem `bài viết
trên Wikipedia `__ này để
bắt đầu). Một số tên gọi/khái niệm thường gặp:
.. raw:: html
- **Thanh ghi** không phải là một bộ phận của bộ nhớ đệm. Chúng hỗ trợ
sắp xếp các câu lệnh cho CPU. Nhưng dù sao thanh ghi cũng là một vùng
nhớ mà CPU có thể truy cập với tốc độ xung nhịp mà không có độ trễ.
Các CPU thường có hàng chục thanh ghi. Việc sử dụng các thanh ghi sao
cho hiệu quả hoàn toàn phụ thuộc vào trình biên dịch (hoặc lập trình
viên). Ví dụ như trong ngôn ngữ C, ta có thể sử dụng từ khóa
``register`` để lưu các biến vào thanh ghi thay vì bộ nhớ.
- Bộ nhớ đệm **L1** là lớp bảo vệ đầu tiên khi nhu cầu băng thông bộ
nhớ quá cao. Bộ nhớ đệm L1 rất nhỏ (kích thước điển hình khoảng 32-64
kB) và thường được chia thành bộ nhớ đệm dữ liệu và câu lệnh. Nếu dữ
liệu được tìm thấy trong bộ nhớ đệm L1, việc truy cập diễn ra rất
nhanh chóng. Nếu không, việc tìm kiếm sẽ tiếp tục theo hệ thống phân
cấp bộ nhớ đệm (*cache hierarchy*).
- Bộ nhớ đệm **L2** là điểm dừng tiếp theo. Vùng nhớ này có thể chuyên
biệt tuỳ theo kiến trúc thiết kế và kích thước vi xử lý. Nó có thể
chỉ được truy cập từ một lõi nhất định hoặc được chia sẻ với nhiều
lõi khác nhau. Bộ nhớ đệm L2 có kích thước lớn hơn (thường là 256-512
kB mỗi lõi) và chậm hơn L1. Hơn nữa, để truy cập vào dữ liệu trong
L2, đầu tiên ta cần kiểm tra để chắc rằng dữ liệu đó không nằm trong
L1, việc này làm tăng độ trễ lên một chút.
- Bộ nhớ đệm **L3** được sử dụng chung cho nhiều lõi khác nhau và có
thể khá lớn. CPU máy chủ Epyc 3 của AMD có bộ nhớ đệm 256MB cực lớn
được phân bổ trên nhiều vi xử lý con (*chiplet*). Thường thì kích
thước của L3 nằm trong khoảng 4-8MB.
.. raw:: html
Việc dự đoán phần tử bộ nhớ nào sẽ cần tiếp theo là một trong những tham
số tối ưu chính trong thiết kế vi xử lý. Ví dụ, việc duyệt *xuôi* bộ nhớ
được coi là thích hợp do đa số các thuật toán ghi đệm (*caching
algorithms*) sẽ cố gắng *đọc về trước* hơn là về sau. Tương tự, việc giữ
hành vi truy cập bộ nhớ ở mức cục bộ là một cách tốt để cải thiện hiệu
năng. Tăng số lượng bộ nhớ đệm là một con dao hai lưỡi. Một mặt việc này
đảm bảo các nhân vi xử lý không bị thiếu dữ liệu. Mặt khác nó tăng kích
thước vi xử lý, lấn chiếm phần diện tích mà đáng ra có thể được sử dụng
vào việc tăng khả năng xử lý. Xét trường hợp tệ nhất như mô tả trong
:numref:`fig_falsesharing`. Một địa chỉ bộ nhớ được lưu trữ tại vi xử
lý 0 trong khi một luồng của vi xử lý 1 yêu cầu dữ liệu đó. Để có thể
lấy dữ liệu, vi xử lý 0 phải dừng công việc đang thực hiện, ghi lại
thông tin vào bộ nhớ chính để vi xử lý 1 đọc dữ liệu từ đó. Trong suốt
quá trình này, cả hai vi xử lý đều ở trong trạng thái chờ. Một đoạn mã
như vậy khả năng cao là sẽ chạy *chậm hơn* trên một hệ đa vi xử lý so
với một vi xử lý đơn được lập trình hiệu quả. Đây là một lý do nữa cho
việc tại sao thực tế phải giới hạn kích thước bộ nhớ đệm (ngoài việc
chiếm diện tích vật lý).
.. raw:: html
.. _fig_falsesharing:
.. figure:: ../img/falsesharing.svg
Chia sẻ dữ liệu lỗi (hình ảnh được sự cho phép của Intel)
.. raw:: html
GPU và các Thiết bị Tăng tốc khác
---------------------------------
.. raw:: html
Không hề phóng đại khi nói rằng học sâu có lẽ sẽ không thành công nếu
không có GPU. Và cũng nhờ có học sâu mà tài sản của các công ty sản suất
GPU tăng trưởng đáng kể. Sự đồng tiến hóa giữa phần cứng và các thuật
toán dẫn tới tình huống mà học sâu trở thành mẫu mô hình thống kê được
ưa thích bất kể có hiệu quả hay không. Do đó, ta cần phải hiểu rõ ràng
lợi ích mà GPU và các thiết bị tăng tốc khác như TPU
:cite:`Jouppi.Young.Patil.ea.2017` mang lại.
.. raw:: html
Ta cần chú ý đến đặc thù thường được sử dụng trong thực tế: thiết bị
tăng tốc được tối ưu hoặc cho bước huấn luyện hoặc cho bước suy luận.
Đối với bước suy luận, ta chỉ cần tính toán lượt truyền xuôi qua mạng,
không cần sử dụng bộ nhớ để lưu dữ liệu trung gian ở bước lan truyền
ngược. Hơn nữa, ta có thể không cần đến phép tính quá chính xác (thường
thì FP16 hoặc INT8 là đủ) Mặt khác trong quá trình huấn luyện, tất cả
kết quả trung gian đều cần phải lưu lại để tính gradient. Hơn nữa, việc
tích luỹ gradient yêu cầu độ chính xác cao hơn nhằm tránh lỗi tràn số
trên hoặc dưới, do đó bước huấn luyện yêu cầu tối thiểu độ chính xác
FP16 (hoặc độ chính xác hỗn hợp khi kết hợp với FP32). Tất cả các yếu tố
trên đòi hỏi bộ nhớ nhanh hơn và lớn hơn (HBM2 hoặc GDDR6) và nhiều khả
năng xử lý hơn. Ví dụ, GPU
`Turing `__
T4 của NVIDIA được tối ưu cho bước suy luận trong khi GPU V100 phù hợp
cho quá trình huấn luyện.
.. raw:: html
Xem lại :numref:`fig_neon128`. Việc thêm các đơn vị vector vào lõi vi
xử lý cho phép ta tăng đáng kể thông lượng xử lý (ở ví dụ trong hình ta
có thể thực hiện 16 thao tác cùng lúc). Chuyện gì sẽ xảy ra nếu ta không
chỉ tối ưu cho phép tính giữa các vector mà còn tối ưu cho các ma trận?
Chiến lược này dẫn tới sự ra đời của Lõi Tensor (chi tiết sẽ được thảo
luận sau đây). Thứ hai, nếu tăng số lượng lõi thì sao? Nói tóm lại, hai
chiến lược trên tóm tắt việc quyết định thiết kế của GPU.
:numref:`fig_turing_processing_block` mô tả tổng quan một khối xử lý
đơn giản, bao gồm 16 đơn vị số nguyên và 16 đơn vị dấu phẩy động. Thêm
vào đó, hai Lõi Tensor xử lý một tập nhỏ các thao thác liên quan đến học
sâu được thêm vào. Mỗi Hệ vi xử lý Luồng (*Streaming Multiprocessor* -
SM) bao gồm bốn khối như vậy.
.. raw:: html
.. _fig_turing_processing_block:
.. figure:: ../img/turing_processing_block.png
:width: 150px
Khối Xử lý Turing của NVIDIA
.. raw:: html
12 hệ vi xử lý luồng sau đó được nhóm vào một cụm xử lý đồ hoạ tạo nên
vi xử lý cao cấp TU102. Số lượng kênh bộ nhớ phong phú và bộ nhớ đệm L2
được bổ sung vào cấu trúc. Thông tin chi tiết được mô tả trong
:numref:`fig_turing`. Một trong những lý do để thiết kế một thiết bị
như vậy là từng khối riêng biệt có thể được thêm vào hoặc bỏ đi tuỳ theo
nhu cầu để có thể tạo thành một vi xử lý nhỏ gọn và giải quyết một số
vấn đề phát sinh (các mô-đun lỗi có thể không được kích hoạt). May mắn
thay, các nhà nghiên cứu học sâu bình thường không cần lập trình cho các
thiết bị này do đã có các lớp mã nguồn framework CUDA ở tầng thấp. Cụ
thể, có thể có nhiều hơn một chương trình được thực thi đồng thời trên
GPU, với điều kiện là còn đủ tài nguyên. Tuy nhiên ta cũng cần để ý đến
giới hạn của các thiết bị nhằm tránh việc lựa chọn mô hình quá lớn so
với bộ nhớ của thiết bị.
.. raw:: html
.. _fig_turing:
.. figure:: ../img/turing.png
:width: 350px
Kiến trúc Turing của NVIDIA (hình ảnh được sự cho phép của NVIDIA)
.. raw:: html
Khía cạnh cuối cùng đáng để bàn luận chi tiết là Lõi Tensor
(*TensorCore*). Đây là một ví dụ của xu hướng gần đây là sử dụng thêm
nhiều mạch đã được tối ưu để tăng hiệu năng cho học sâu. Ví dụ, TPU có
thêm một mảng tâm thu (*systolic array*) :cite:`Kung.1988` để tăng tốc
độ nhân ma trận. Thiết kế của TPU chỉ hỗ trợ một số lượng rất ít các
phép tính kích thước lớn (thế hệ TPU đầu tiên hỗ trợ một phép tính). Lõi
Tensor thì ngược lại, được tối ưu cho các phép tính kích thước nhỏ cho
các ma trận kích thước 4x4 đến 16x16, tuỳ vào độ chính xác số học.
:numref:`fig_tensorcore` mô tả tổng quan quá trình tối ưu.
.. raw:: html
.. _fig_tensorcore:
.. figure:: ../img/turing.png
:width: 400px
Lõi Tensor của NVIDIA trong Turing (hình ảnh được sự cho phép của
NVIDIA)
.. raw:: html
Đương nhiên khi tối ưu cho quá trình tính toán, ta buộc phải có một số
đánh đổi nhất định. Một trong số đó là GPU không xử lý tốt dữ liệu ngắt
quãng hoặc thưa. Trừ một số ngoại lệ đáng chú ý, ví dụ như
`Gunrock `__
:cite:`Wang.Davidson.Pan.ea.2016`, việc truy cập vector và ma trận
thưa không phù hợp với các thao tác đọc theo cụm (*burst read*) với băng
thông cao của GPU. Đạt được cả hai mục tiêu là một lĩnh vực đang được
đẩy mạnh nghiên cứu. Ví dụ, tham khảo `DGL `__, một thư
viện được điều chỉnh cho phù hợp với học sâu trên đồ thị.
.. raw:: html
Mạng máy tính và Bus
--------------------
.. raw:: html
Mỗi khi một thiết bị đơn không đủ cho quá trình tối ưu, ta cần chuyển dữ
liệu đến và đi khỏi nó để đồng bộ hóa quá trình xử lý. Đây chính là lúc
mà mạng máy tính và bus trở nên hữu dụng. Ta có một vài tham số thiết kế
gồm: băng thông, chi phí, khoảng cách và tính linh hoạt. Tuy ta cũng có
Wifi với phạm vi hoạt động tốt, dễ dàng để sử dụng (dù sao cũng là không
dây), rẻ nhưng lại có băng thông không quá tốt và độ trễ lớn. Sẽ không
có bất cứ nhà nghiên cứu học máy tỉnh táo nào lại nghĩ đến việc sử dụng
Wifi để xây dựng một cụm máy chủ. Sau đây, ta sẽ chỉ tập trung vào các
cách kết nối phù hợp cho học sâu.
.. raw:: html
- **PCIe** là một bus riêng chỉ phục vụ cho kết nối điểm – điểm với
băng thông trên mỗi làn rất lớn (lên đến 16 GB/s trên PCIe 4.0). Độ
trễ thường có giá trị cỡ vài micro giây (5 μs). Kết nối PCIe khá quan
trọng. Vi xử lý chỉ có một số lượng làn PCIe nhất định: EPYC 3 của
AMD có 128 làn, Xeon của Intel lên đến 48 làn cho mỗi chip; trên CPU
dùng cho máy tính để bàn, số lượng này lần lượt là 20 (với Ryzen 9)
và 16 (với Core i9). Do GPU thường có 16 luồng nên số lượng GPU có
thể kết nối với CPU bị giới hạn tại băng thông tối đa. Xét cho cùng,
chúng cần chia sẻ liên kết với các thiết bị ngoại vi khác như bộ nhớ
và cổng Ethernet. Giống như việc truy cập RAM, việc truyền lượng lớn
dữ liệu thường được ưa chuộng hơn nhằm giảm tổng chi phí theo gói
tin.
- **Ethernet** là cách phổ biến nhất để kết nối máy tính với nhau. Dù
nó chậm hơn đáng kể so với PCIe, nó rất rẻ và dễ cài đặt, bao phủ
khoảng cách lớn hơn nhiều. Băng thông đặc trưng đối với máy chủ cấp
thấp là 1 GBit/s. Các thiết bị cao cấp hơn (ví dụ như `máy chủ loại
C5 `__ trên AWS) cung
cấp băng thông từ 10 đến 100 GBit/s. Cũng như các trường hợp trên,
việc truyền dữ liệu có tổng chi phí đáng kể. Chú ý rằng ta hầu như
không bao giờ sử dụng trực tiếp Ethernet thuần mà sử dụng một giao
thức được thực thi ở tầng trên của kết nối vật lý (ví dụ như UDP hay
TCP/IP). Việc này làm tăng tổng chi phí. Giống như PCIe, Ethernet
được thiết kế để kết nối hai thiết bị, ví dụ như máy tính với một bộ
chuyển mạch (*switch*).
- **Bộ chuyển mạch** cho phép ta kết nối nhiều thiết bị theo cách mà
bất cứ cặp thiết bị nào cũng có thể (thường là với băng thông tối đa)
thực hiện kết nối điểm – điểm cùng lúc. Ví dụ, bộ chuyển mạch
Ethernet có thể kết nối 40 máy chủ với băng thông xuyên vùng
(*cross-sectional bandwidth*) cao. Chú ý rằng bộ chuyển mạch không
phải chỉ có trong mạng máy tính truyền thống. Ngay cả làn PCIe cũng
có thể `chuyển
mạch `__.
Điều này xảy ra khi kết nối một lượng lớn GPU tới vi xử lý chính, như
với trường hợp `máy chủ loại
P2 `__.
- **NVLink** là một phương pháp thay thế PCIe khi ta cần kết nối với
băng thông rất lớn. NVLink cung cấp tốc độ truyền dữ liệu lên đến 300
Gbit/s mỗi đường dẫn (*link*). GPU máy chủ (Volta V100) có 6 đường
dẫn, trong khi GPU thông dụng (RTX 2080 Ti) chỉ có một đường dẫn,
hoạt động ở tốc độ thấp 100 Gbit/s. Vì vậy, chúng tôi gợi ý sử dụng
`NCCL `__ để có thể đạt được tốc độ
truyền dữ liệu cao giữa các GPU.
Tóm tắt
-------
.. raw:: html
- Các thiết bị đều có chi phí phụ trợ trên mỗi hành động. Do đó ta nên
nhắm tới việc di chuyển ít lần các lượng dữ liệu lớn thay vì di
chuyển nhiều lần các lượng dữ liệu nhỏ. Điều này đúng với RAM, SSD,
các thiết bị mạng và GPU.
- Vector hóa rất quan trọng để tăng hiệu năng. Hãy đảm bảo bạn hiểu các
điểm mạnh đặc thù của thiết bị tăng tốc mình đang có. Ví dụ, một vài
CPU Intel Xeon thực hiện cực kỳ hiệu quả phép toán với dữ liệu kiểu
INT8, GPU NVIDIA Volta rất phù hợp với các phép toán với ma trận dữ
liệu kiểu FP16; còn NVIDIA Turing chạy tốt cho cả các phép toán với
dữ liệu kiểu FP16, INT8, INT4.
- Hiện tượng tràn số trên do kiểu dữ liệu không đủ số bit để biểu diễn
giá trị có thể là một vấn đề khi huấn luyện (và cả khi suy luận, dù
ít nghiêm trọng hơn).
- Việc cùng dữ liệu nhưng có nhiều địa chỉ (*aliasing*) có thể làm giảm
đáng kể hiệu năng. Ví dụ, việc sắp xếp dữ liệu trong bộ nhớ (*memory
alignment*) trên CPU 64 bit nên được thực hiện theo từng khối 64 bit.
Trên GPU, tốt hơn là nên giữ kích thước tích chập đồng bộ, với
TensorCores chẳng hạn.
- Sử dụng thuật toán phù hợp với phần cứng (về mức chiếm dụng bộ nhớ,
băng thông, v.v). Thời gian thực thi có thể giảm hàng trăm ngàn lần
khi tất cả tham số đều được chứa trong bộ đệm.
- Chúng tôi khuyến khích bạn đọc tính toán trước hiệu năng của một
thuật toán mới trước khi kiểm tra bằng thực nghiệm. Sự khác biệt lên
tới hàng chục lần hoặc hơn là dấu hiệu cần quan tâm.
- Sử dụng các công cụ phân tích hiệu năng (*profiler*) để tìm điểm
nghẽn cổ chai của hệ thống.
- Phần cứng sử dụng cho huấn luyện và suy luận có các cấu hình hiệu quả
khác nhau để cân đối giá tiền và hiệu năng.
.. raw:: html
Độ trễ
------
.. raw:: html
Các thông tin trong :numref:`table_latency_numbers` và
:numref:`table_latency_numbers_tesla` được `Eliot
Eshelman `__ duy trì cập nhật trên
`GitHub
Gist `__.
.. raw:: html
:Các độ trễ thường gặp.
.. raw:: html
.. table:: label:\ ``table_latency_numbers``
+-----------------------------------+----+----------------------------+
| Hoạt động | Th | Chú thích |
| | ời | |
| | gi | |
| | an | |
+===================================+====+============================+
| Truy xuất bộ đệm L1 | 1. | 4 chu kỳ |
| | 5 | |
| | ns | |
+-----------------------------------+----+----------------------------+
| Cộng, nhân, cộng kết hợp nhân | 1. | 4 chu kỳ |
| (*FMA*) số thực dấu phẩy động | 5 | |
| | ns | |
+-----------------------------------+----+----------------------------+
| Truy xuất bộ đệm L2 | 5 | 12 ~ 17 chu kỳ |
| | ns | |
+-----------------------------------+----+----------------------------+
| Rẽ nhánh sai | 6 | 15 ~ 20 chu kỳ |
| | ns | |
+-----------------------------------+----+----------------------------+
| Truy xuất bộ đệm L3 (không chia | 16 | 42 chu kỳ |
| sẻ) | ns | |
+-----------------------------------+----+----------------------------+
| Truy xuất bộ đệm L3 (chia sẻ với | 25 | 65 chu kỳ |
| nhân khác) | ns | |
+-----------------------------------+----+----------------------------+
| Khóa/mở đèn báo lập trình | 25 | |
| (*mutex*) | ns | |
+-----------------------------------+----+----------------------------+
| Truy xuất bộ đệm L3 (được nhân | 29 | 75 chu kỳ |
| khác thay đổi) | ns | |
+-----------------------------------+----+----------------------------+
| Truy xuất bộ đệm L3 (tại CPU | 40 | 100 ~ 300 chu kỳ (40 ~ 116 |
| socket từ xa) | ns | ns) |
+-----------------------------------+----+----------------------------+
| QPI hop đến CPU khác (cho mỗi | 40 | |
| hop) | ns | |
+-----------------------------------+----+----------------------------+
| Truy xuất 64MB (CPU cục bộ) | 46 | TinyMemBench trên |
| | ns | Broadwell E5-2690v4 |
+-----------------------------------+----+----------------------------+
| Truy xuất 64MB (CPU từ xa) | 70 | TinyMemBench trên |
| | ns | Broadwell E5-2690v4 |
+-----------------------------------+----+----------------------------+
| Truy xuất 256MB (CPU cục bộ) | 75 | TinyMemBench trên |
| | ns | Broadwell E5-2690v4 |
+-----------------------------------+----+----------------------------+
| Ghi ngẫu nhiên vào Intel Optane | 94 | UCSD Non-Volatile Systems |
| | ns | Lab |
+-----------------------------------+----+----------------------------+
| Truy xuất 256MB (CPU từ xa) | 12 | TinyMemBench trên |
| | 0 | Broadwell E5-2690v4 |
| | ns | |
+-----------------------------------+----+----------------------------+
| Đọc ngẫu nhiên từ Intel Optane | 30 | UCSD Non-Volatile Systems |
| | 5 | Lab |
| | ns | |
+-----------------------------------+----+----------------------------+
| Truyền 4KB trên sợi HPC 100 Gbps | 1 | MVAPICH2 trên Intel |
| | μs | Omni-Path |
+-----------------------------------+----+----------------------------+
| Nén 1KB với Google Snappy | 3 | |
| | μs | |
+-----------------------------------+----+----------------------------+
| Truyền 4KB trên cáp mạng 10 Gbps | 10 | |
| | μs | |
+-----------------------------------+----+----------------------------+
| Ghi ngẫu nhiên 4KB vào SSD NVMe | 30 | DC P3608 SSD NVMe (QOS 99% |
| | μs | khoảng 500μs) |
+-----------------------------------+----+----------------------------+
| Truyền 1MB từ/đến NVLink GPU | 30 | ~33GB/s trên NVIDIA 40GB |
| | μs | NVLink |
+-----------------------------------+----+----------------------------+
| Truyền 1MB từ/đến PCI-E GPU | 80 | ~12GB/s trên PCIe 3.0 x16 |
| | μs | link |
+-----------------------------------+----+----------------------------+
| Đọc ngẫu nhiên 4KB từ SSD NVMe | 12 | DC P3608 SSD NVMe (QOS |
| | 0 | 99%) |
| | μs | |
+-----------------------------------+----+----------------------------+
| Đọc tuần tự 1MB từ SSD NVMe | 20 | ~4.8GB/s DC P3608 SSD NVMe |
| | 8 | |
| | μs | |
+-----------------------------------+----+----------------------------+
| Ghi ngẫu nhiên 4KB vào SSD SATA | 50 | DC S3510 SSD SATA (QOS |
| | 0 | 99.9%) |
| | μs | |
+-----------------------------------+----+----------------------------+
| Đọc ngẫu nhiên 4KB từ SSD SATA | 50 | DC S3510 SSD SATA (QOS |
| | 0 | 99.9%) |
| | μs | |
+-----------------------------------+----+----------------------------+
| Truyền 2 chiều trong cùng trung | 50 | Ping một chiều ~250μs |
| tâm dữ liệu | 0 | |
| | μs | |
+-----------------------------------+----+----------------------------+
| Đọc tuần tự 1MB từ SSD SATA | 2 | ~550MB/s DC S3510 SSD SATA |
| | ms | |
+-----------------------------------+----+----------------------------+
| Đọc tuần tự 1MB từ ổ đĩa | 5 | ~200MB/s server HDD |
| | ms | |
+-----------------------------------+----+----------------------------+
| Truy cập ngẫu nhiên ổ đĩa (tìm + | 10 | |
| xoay) | ms | |
+-----------------------------------+----+----------------------------+
| Gửi gói dữ liệu từ California -> | 15 | |
| Hà Lan -> California | 0 | |
| | ms | |
+-----------------------------------+----+----------------------------+
.. raw:: html
.. raw:: html
.. raw:: html
:Độ trễ của GPU NVIDIA Tesla.
.. raw:: html
.. table:: label:\ ``table_latency_numbers_tesla``
+-------------------------+-------+-----------------------------------+
| Hoạt động | Thời | Chú thích |
| | gian | |
+=========================+=======+===================================+
| Truy cập bộ nhớ chung | 30 ns | 30~90 chu kỳ (tính cả xung đột |
| của GPU | | của các bank) |
+-------------------------+-------+-----------------------------------+
| Truy cập bộ nhớ toàn | 200 | 200~800 chu kỳ |
| cục của GPU | ns | |
+-------------------------+-------+-----------------------------------+
| Khởi chạy nhân CUDA | 10 μs | CPU host ra lệnh cho GPU khởi |
| trên GPU | | chạy nhân |
+-------------------------+-------+-----------------------------------+
| Truyền 1MB từ/đến GPU | 30 μs | ~33GB/s trên NVIDIA NVLink 40GB |
| NVLink | | |
+-------------------------+-------+-----------------------------------+
| Truyền 1MB từ/đến GPU | 80 μs | ~12GB/s trên PCI-Express link x16 |
| PCI-E | | |
+-------------------------+-------+-----------------------------------+
Bài tập
-------
.. raw:: html
1. Viết đoạn mã C để so sánh tốc độ khi truy cập bộ nhớ được sắp xếp
theo khối (*aligned memory*) với khi truy cập bộ nhớ không được sắp
xếp như vậy (một cách tương đối so với bộ nhớ ngoài). **Gợi ý:** hãy
loại bỏ hiệu ứng của bộ nhớ đệm.
2. So sánh tốc độ khi truy cập bộ nhớ tuần tự với khi truy cập theo sải
bước cho trước.
3. Làm thế nào để đo kích thước bộ nhớ đệm trên CPU?
4. Bạn sẽ sắp xếp dữ liệu trên nhiều bộ nhớ như thế nào để có băng
thông tối đa? Sắp xếp như thế nào nếu bạn có nhiều luồng nhỏ?
5. Tốc độ quay của một ổ cứng HDD dùng cho công nghiệp là 10,000 rpm.
Thời gian tối thiểu mà HDD đó cần (trong trường hợp tệ nhất) trước
khi có thể đọc dữ liệu là bao nhiêu (có thể giả sử các đầu đọc ổ đĩa
di chuyển tức thời)?
6. Giả sử nhà sản xuất HDD tăng sức chứa bộ nhớ từ 1 Tbit mỗi inch
vuông lên 5 Tbit mỗi inch vuông. Có thể lưu bao nhiêu dữ liệu trên
một đĩa từ của một HDD 2.5"? Có sự khác biệt nào giữa track trong và
track ngoài không?
7. Một máy chủ loại P2 trên AWS có 16 GPU K80 Kepler. Sử dụng lệnh
``lspci`` trên một máy p2.16xlarge và một máy p2.8xlarge để hiểu
cách các GPU được kết nối với các CPU. **Gợi ý:** để ý đến chip cầu
nối PLX cho chuẩn kết nối PCI.
8. Chuyển từ kiểu dữ liệu 8 bit sang 16 bit cần lượng silicon gấp 4
lần. Tại sao? Tại sao NVIDIA thêm các phép toán cho kiểu dữ liệu
INT4 vào GPU Turing?
9. Có 6 đường truyền tốc độ cao giữa các GPU (như GPU Volta V100 chẳng
hạn), bạn sẽ kết nối 8 GPU đó như thế nào? Tham khảo cách kết nối
cho máy chủ p3.16xlarge trên AWS.
10. Đọc xuôi bộ nhớ nhanh gấp bao nhiêu lần đọc ngược? Sự chênh lệch này
có khác nhau giữa các nhà sản xuất máy tính và CPU không? Tại sao?
Thí nghiệm với mã nguồn C.
11. Bạn có thể đo kích thước bộ nhớ đệm trên ổ đĩa của mình không? Bộ
nhớ đệm trên HDD là gì? SSD có cần bộ nhớ đệm không?
12. Chi phí bộ nhớ phụ trợ khi gửi một gói dữ liệu qua cáp mạng
(*Ethernet*) là bao nhiêu. So sánh các giao thức UDP và TCP/IP.
13. Truy cập Bộ nhớ Trực tiếp (*Direct Memory Access*) cho phép các
thiết bị khác ngoài CPU ghi (và đọc) trực tiếp vào (từ) bộ nhớ. Tại
sao đây là một ý tưởng hay?
14. Nhìn vào thông số hiệu năng của GPU Turing T4. Tại sao hiệu năng
*chỉ* tăng gấp đôi khi chuyển từ phép toán với kiểu dữ liệu FP16
sang INT8 và INT4?
15. Thời gian truyền một gói dữ liệu hai chiều giữa San Francisco và
Amsterdam là bao nhiêu? **Gợi ý:** giả sử khoảng cách giữa 2 thành
phố là 10,000km.
Thảo luận
---------
- `Tiếng Anh `__
- `Tiếng Việt `__
Những người thực hiện
---------------------
Bản dịch trong trang này được thực hiện bởi:
- Đoàn Võ Duy Thanh
- Nguyễn Văn Quang
- Phạm Minh Đức
- Lê Khắc Hồng Phúc
- Nguyễn Văn Cường
- Nguyễn Mai Hoàng Long
- Trần Yến Thy
- Nguyễn Thanh Hòa
- Đỗ Trường Giang
- Phạm Hồng Vinh