Tổng quan
Hướng dẫn này sẽ chỉ cho bạn cách sử dụng TensorFlow Profiler với TensorBoard để hiểu rõ hơn và đạt được hiệu suất tối đa từ GPU của bạn cũng như gỡ lỗi khi một hoặc nhiều GPU của bạn không được sử dụng đúng mức.
Nếu bạn chưa quen với Trình hồ sơ:
- Bắt đầu với TensorFlow Profiler: Sổ ghi chép hiệu suất mô hình hồ sơ với ví dụ về Keras và TensorBoard .
- Tìm hiểu về các công cụ và phương pháp lập hồ sơ khác nhau có sẵn để tối ưu hóa hiệu suất TensorFlow trên máy chủ (CPU) với Tối ưu hóa hiệu suất TensorFlow bằng hướng dẫn Profiler .
Hãy nhớ rằng việc giảm tải tính toán cho GPU có thể không phải lúc nào cũng có lợi, đặc biệt đối với các mô hình nhỏ. Có thể có chi phí chung do:
- Truyền dữ liệu giữa máy chủ (CPU) và thiết bị (GPU); Và
- Do độ trễ liên quan khi máy chủ khởi chạy hạt nhân GPU.
Quy trình làm việc tối ưu hóa hiệu suất
Hướng dẫn này phác thảo cách khắc phục các vấn đề về hiệu suất, bắt đầu bằng một GPU, sau đó chuyển sang một máy chủ có nhiều GPU.
Nên gỡ lỗi các vấn đề về hiệu suất theo thứ tự sau:
- Tối ưu hóa và gỡ lỗi hiệu suất trên một GPU:
- Kiểm tra xem đường ống đầu vào có bị tắc nghẽn hay không.
- Gỡ lỗi hiệu suất của một GPU.
- Bật độ chính xác hỗn hợp (với
fp16
(float16)) và tùy chọn bật XLA .
- Tối ưu hóa và gỡ lỗi hiệu suất trên máy chủ đơn đa GPU.
Ví dụ: nếu bạn đang sử dụng chiến lược phân phối TensorFlow để huấn luyện một mô hình trên một máy chủ có nhiều GPU và nhận thấy mức sử dụng GPU dưới mức tối ưu, trước tiên bạn nên tối ưu hóa và gỡ lỗi hiệu suất cho một GPU trước khi gỡ lỗi hệ thống nhiều GPU.
Là cơ sở để nhận mã hiệu suất trên GPU, hướng dẫn này giả định rằng bạn đã sử dụng tf.function
. API Keras Model.compile
và Model.fit
sẽ tự động sử dụng tf.function
. Khi viết vòng lặp đào tạo tùy chỉnh bằng tf.GradientTape
, hãy tham khảo Hiệu suất tốt hơn với tf.function về cách bật tf.function
s.
Các phần tiếp theo thảo luận về các phương pháp được đề xuất cho từng tình huống trên để giúp xác định và khắc phục các tắc nghẽn về hiệu suất.
1. Tối ưu hóa hiệu suất trên một GPU
Trong trường hợp lý tưởng, chương trình của bạn phải có mức sử dụng GPU cao, giao tiếp giữa CPU (máy chủ) với GPU (thiết bị) ở mức tối thiểu và không có chi phí từ đường dẫn đầu vào.
Bước đầu tiên trong việc phân tích hiệu suất là lấy hồ sơ cho một mô hình chạy với một GPU.
Trang tổng quan về Profiler của TensorBoard —hiển thị chế độ xem cấp cao nhất về cách mô hình của bạn hoạt động trong quá trình chạy hồ sơ — có thể cung cấp ý tưởng về khoảng cách giữa chương trình của bạn với kịch bản lý tưởng.
Những con số quan trọng cần chú ý ở trang tổng quan là:
- Bao nhiêu thời gian của bước là từ việc thực thi thiết bị thực tế
- Tỷ lệ phần trăm hoạt động được đặt trên thiết bị so với máy chủ
- Có bao nhiêu kernel sử dụng
fp16
Đạt được hiệu suất tối ưu có nghĩa là tối đa hóa những con số này trong cả ba trường hợp. Để hiểu sâu hơn về chương trình của bạn, bạn cần phải làm quen với trình xem dấu vết Profiler của TensorBoard. Các phần bên dưới hiển thị một số mẫu trình xem dấu vết phổ biến mà bạn nên tìm kiếm khi chẩn đoán tắc nghẽn về hiệu suất.
Dưới đây là hình ảnh của chế độ xem theo dõi mô hình đang chạy trên một GPU. Từ các phần Phạm vi tên TensorFlow và TensorFlow Ops , bạn có thể xác định các phần khác nhau của mô hình, như chuyển tiếp, hàm mất, tính toán chuyển tiếp/độ dốc lùi và cập nhật trọng số của trình tối ưu hóa. Bạn cũng có thể đặt các hoạt động chạy trên GPU bên cạnh mỗi Luồng , đề cập đến các luồng CUDA. Mỗi luồng được sử dụng cho các nhiệm vụ cụ thể. Trong dấu vết này, Stream#118 được sử dụng để khởi chạy hạt nhân điện toán và bản sao từ thiết bị này sang thiết bị khác. Luồng#119 được sử dụng để sao chép từ máy chủ sang thiết bị và Luồng#120 để sao chép từ thiết bị sang máy chủ.
Dấu vết dưới đây cho thấy các đặc điểm chung của một mô hình hoạt động.
Ví dụ: dòng thời gian tính toán GPU ( Stream#118 ) có vẻ "bận rộn" với rất ít khoảng trống. Có các bản sao tối thiểu từ máy chủ đến thiết bị ( Luồng #119 ) và từ thiết bị đến máy chủ ( Luồng #120 ), cũng như khoảng cách tối thiểu giữa các bước. Khi chạy Profiler cho chương trình của mình, bạn có thể không xác định được những đặc điểm lý tưởng này trong chế độ xem theo dõi của mình. Phần còn lại của hướng dẫn này bao gồm các tình huống phổ biến và cách khắc phục chúng.
1. Gỡ lỗi đường dẫn đầu vào
Bước đầu tiên trong quá trình gỡ lỗi hiệu suất GPU là xác định xem chương trình của bạn có bị giới hạn đầu vào hay không. Cách dễ nhất để tìm ra điều này là sử dụng bộ phân tích đường dẫn đầu vào của Profiler trên TensorBoard, công cụ này cung cấp cái nhìn tổng quan về thời gian dành cho đường dẫn đầu vào.
Bạn có thể thực hiện các hành động tiềm năng sau nếu đường dẫn đầu vào của bạn đóng góp đáng kể vào thời gian của bước:
- Bạn có thể sử dụng hướng dẫn cụ thể về
tf.data
để tìm hiểu cách gỡ lỗi đường dẫn đầu vào của mình. - Một cách nhanh chóng khác để kiểm tra xem đường dẫn đầu vào có bị tắc nghẽn hay không là sử dụng dữ liệu đầu vào được tạo ngẫu nhiên mà không cần xử lý trước. Đây là một ví dụ về việc sử dụng kỹ thuật này cho mô hình ResNet. Nếu quy trình đầu vào tối ưu, bạn sẽ trải nghiệm hiệu suất tương tự với dữ liệu thực và với dữ liệu ngẫu nhiên/tổng hợp được tạo. Chi phí duy nhất trong trường hợp dữ liệu tổng hợp sẽ là do sao chép dữ liệu đầu vào, dữ liệu này có thể được tìm nạp trước và tối ưu hóa một lần nữa.
Ngoài ra, hãy tham khảo các phương pháp hay nhất để tối ưu hóa đường dẫn dữ liệu đầu vào .
2. Gỡ lỗi hiệu suất của một GPU
Có một số yếu tố có thể góp phần vào việc sử dụng GPU thấp. Dưới đây là một số tình huống thường thấy khi xem xét trình xem dấu vết và các giải pháp tiềm năng.
1. Phân tích khoảng cách giữa các bước
Một quan sát phổ biến khi chương trình của bạn chạy không tối ưu là khoảng cách giữa các bước đào tạo. Trong hình ảnh của chế độ xem theo dõi bên dưới, có một khoảng cách lớn giữa bước 8 và 9, nghĩa là GPU không hoạt động trong thời gian đó.
Nếu trình xem dấu vết của bạn hiển thị khoảng cách lớn giữa các bước, đây có thể là dấu hiệu cho thấy chương trình của bạn bị giới hạn đầu vào. Trong trường hợp đó, bạn nên tham khảo phần trước về cách gỡ lỗi đường dẫn đầu vào nếu bạn chưa làm như vậy.
Tuy nhiên, ngay cả với quy trình đầu vào được tối ưu hóa, bạn vẫn có thể có khoảng trống giữa phần cuối của bước này và phần bắt đầu của bước khác do xung đột luồng CPU. tf.data
sử dụng các luồng nền để song song hóa quá trình xử lý đường ống. Các luồng này có thể cản trở hoạt động phía máy chủ GPU diễn ra ở đầu mỗi bước, chẳng hạn như sao chép dữ liệu hoặc lên lịch hoạt động của GPU.
Nếu bạn nhận thấy những khoảng trống lớn ở phía máy chủ lên lịch cho các hoạt động này trên GPU, bạn có thể đặt biến môi trường TF_GPU_THREAD_MODE=gpu_private
. Điều này đảm bảo rằng các nhân GPU được khởi chạy từ các luồng chuyên dụng của riêng chúng và không bị xếp hàng sau công việc tf.data
.
Khoảng cách giữa các bước cũng có thể do tính toán số liệu, lệnh gọi lại Keras hoặc các hoạt động bên ngoài tf.function
chạy trên máy chủ gây ra. Các op này không có hiệu suất tốt bằng các op trong biểu đồ TensorFlow. Ngoài ra, một số hoạt động này chạy trên CPU và sao chép các tensor qua lại từ GPU.
Nếu sau khi tối ưu hóa quy trình đầu vào mà bạn vẫn nhận thấy khoảng cách giữa các bước trong trình xem theo dõi, thì bạn nên xem mã mô hình giữa các bước và kiểm tra xem việc tắt lệnh gọi lại/số liệu có cải thiện hiệu suất hay không. Một số chi tiết về các hoạt động này cũng có trên trình xem theo dõi (cả thiết bị và phía máy chủ). Khuyến nghị trong trường hợp này là khấu hao chi phí chung của các hoạt động này bằng cách thực hiện chúng sau một số bước cố định thay vì mỗi bước. Khi sử dụng phương thức Model.compile
trong API tf.keras
, việc đặt cờ steps_per_execution
sẽ tự động thực hiện việc này. Đối với các vòng đào tạo tùy chỉnh, hãy sử dụng tf.while_loop
.
2. Đạt được mức sử dụng thiết bị cao hơn
1. Độ trễ khởi chạy hạt nhân GPU nhỏ và hạt nhân máy chủ
Máy chủ xếp các hạt nhân vào hàng đợi để chạy trên GPU, nhưng có độ trễ (khoảng 20-40 μs) trước khi các hạt nhân thực sự được thực thi trên GPU. Trong trường hợp lý tưởng, máy chủ xếp đủ số hạt nhân vào hàng đợi trên GPU sao cho GPU dành phần lớn thời gian để thực thi thay vì chờ máy chủ xếp thêm hạt nhân vào hàng đợi.
Trang tổng quan của Profiler trên TensorBoard cho biết GPU không hoạt động trong bao lâu do phải chờ máy chủ khởi chạy kernel. Trong hình ảnh bên dưới, GPU không hoạt động trong khoảng 10% thời gian chờ hạt nhân được khởi chạy.
Trình xem dấu vết cho cùng chương trình này hiển thị những khoảng trống nhỏ giữa các hạt nhân nơi máy chủ đang bận khởi chạy hạt nhân trên GPU.
Bằng cách khởi chạy nhiều hoạt động nhỏ trên GPU (chẳng hạn như phép cộng vô hướng), máy chủ có thể không theo kịp GPU. Công cụ TensorFlow Stats trong TensorBoard cho cùng một Cấu hình hiển thị 126.224 thao tác Mul mất 2,77 giây. Do đó, mỗi hạt nhân có kích thước khoảng 21,9 μs, rất nhỏ (tương đương với độ trễ khởi chạy) và có thể dẫn đến độ trễ khởi chạy hạt nhân máy chủ.
Nếu trình xem dấu vết của bạn hiển thị nhiều khoảng trống nhỏ giữa các hoạt động trên GPU như trong hình trên, bạn có thể:
- Ghép nối các tensor nhỏ và sử dụng các hoạt động được vector hóa hoặc sử dụng kích thước lô lớn hơn để làm cho mỗi hạt nhân được khởi chạy thực hiện được nhiều công việc hơn, điều này sẽ khiến GPU bận rộn lâu hơn.
- Đảm bảo rằng bạn đang sử dụng
tf.function
để tạo biểu đồ TensorFlow để bạn không chạy các hoạt động ở chế độ háo hức thuần túy. Nếu bạn đang sử dụngModel.fit
(ngược lại với vòng đào tạo tùy chỉnh vớitf.GradientTape
), thìtf.keras.Model.compile
sẽ tự động thực hiện việc này cho bạn. - Hợp nhất các hạt nhân sử dụng XLA với
tf.function(jit_compile=True)
hoặc tự động phân cụm. Để biết thêm chi tiết, hãy đi tới phần Bật độ chính xác hỗn hợp và XLA bên dưới để tìm hiểu cách bật XLA để đạt hiệu suất cao hơn. Tính năng này có thể dẫn đến việc sử dụng thiết bị cao.
2. Vị trí đặt TensorFlow
Trang tổng quan về Profiler hiển thị cho bạn tỷ lệ phần trăm hoạt động được đặt trên máy chủ so với thiết bị (bạn cũng có thể xác minh vị trí của các hoạt động cụ thể bằng cách xem trình xem theo dõi . Giống như trong hình ảnh bên dưới, bạn muốn tỷ lệ phần trăm hoạt động trên máy chủ rất nhỏ so với thiết bị.
Lý tưởng nhất là hầu hết các hoạt động tính toán chuyên sâu nên được đặt trên GPU.
Để tìm hiểu xem các phép toán và tensor trong mô hình của bạn được gán cho thiết bị nào, hãy đặt tf.debugging.set_log_device_placement(True)
làm câu lệnh đầu tiên trong chương trình của bạn.
Lưu ý rằng trong một số trường hợp, ngay cả khi bạn chỉ định một op được đặt trên một thiết bị cụ thể, việc triển khai nó có thể ghi đè điều kiện này (ví dụ: tf.unique
). Ngay cả đối với đào tạo GPU đơn lẻ, việc chỉ định chiến lược phân phối, chẳng hạn như tf.distribute.OneDeviceStrategy
, có thể dẫn đến vị trí hoạt động mang tính quyết định hơn trên thiết bị của bạn.
Một lý do để đặt phần lớn các hoạt động trên GPU là để ngăn chặn việc sao chép bộ nhớ quá mức giữa máy chủ và thiết bị (dự kiến sẽ có bản sao bộ nhớ cho dữ liệu đầu vào/đầu ra của mô hình giữa máy chủ và thiết bị). Một ví dụ về sao chép quá mức được thể hiện trong chế độ xem theo dõi bên dưới trên các luồng GPU #167 , #168 và #169 .
Những bản sao này đôi khi có thể ảnh hưởng đến hiệu suất nếu chúng chặn hoạt động của nhân GPU. Các thao tác sao chép bộ nhớ trong trình xem theo dõi có nhiều thông tin hơn về các op là nguồn gốc của các tensor được sao chép này, nhưng không phải lúc nào cũng dễ dàng liên kết một memCopy với một op. Trong những trường hợp này, sẽ rất hữu ích khi xem xét các hoạt động gần đó để kiểm tra xem việc sao chép bộ nhớ có diễn ra ở cùng một vị trí trong mỗi bước hay không.
3. Hạt nhân hiệu quả hơn trên GPU
Khi mức sử dụng GPU của chương trình của bạn ở mức chấp nhận được, bước tiếp theo là xem xét việc tăng hiệu quả của nhân GPU bằng cách sử dụng Tensor Cores hoặc các hoạt động hợp nhất.
1. Sử dụng lõi Tensor
GPU NVIDIA® hiện đại có lõi Tensor chuyên dụng có thể cải thiện đáng kể hiệu suất của các hạt nhân đủ điều kiện.
Bạn có thể sử dụng số liệu thống kê nhân GPU của TensorBoard để trực quan hóa nhân GPU nào đủ điều kiện sử dụng Tensor Core và nhân nào đang sử dụng Tensor Cores. Kích hoạt fp16
(xem phần Kích hoạt Độ chính xác Hỗn hợp bên dưới) là một cách để làm cho hạt nhân Nhân ma trận chung (GEMM) (matmul ops) trong chương trình của bạn sử dụng Tensor Core. Nhân GPU sử dụng Lõi Tensor một cách hiệu quả khi độ chính xác là fp16 và kích thước tensor đầu vào/đầu ra chia hết cho 8 hoặc 16 (đối với int8
).
Để biết các đề xuất chi tiết khác về cách làm cho hạt nhân hoạt động hiệu quả cho GPU, hãy tham khảo hướng dẫn hiệu suất học sâu của NVIDIA® .
2. Hoạt động cầu chì
Sử dụng tf.function(jit_compile=True)
để hợp nhất các hoạt động nhỏ hơn để tạo thành các hạt nhân lớn hơn dẫn đến tăng hiệu suất đáng kể. Để tìm hiểu thêm, hãy tham khảo hướng dẫn XLA .
3. Kích hoạt độ chính xác hỗn hợp và XLA
Sau khi làm theo các bước trên, bật độ chính xác hỗn hợp và XLA là hai bước tùy chọn mà bạn có thể thực hiện để cải thiện hiệu suất hơn nữa. Cách tiếp cận được đề xuất là kích hoạt từng cái một và xác minh rằng các lợi ích về hiệu suất có như mong đợi.
1. Kích hoạt độ chính xác hỗn hợp
Hướng dẫn về độ chính xác hỗn hợp TensorFlow chỉ ra cách bật độ chính xác fp16
trên GPU. Kích hoạt AMP trên GPU NVIDIA® để sử dụng Tensor Cores và đạt được tốc độ tổng thể lên tới gấp 3 lần so với việc chỉ sử dụng độ chính xác fp32
(float32) trên Volta và các kiến trúc GPU mới hơn.
Đảm bảo rằng kích thước ma trận/tensor đáp ứng các yêu cầu để gọi hạt nhân sử dụng Lõi Tensor. Nhân GPU sử dụng Lõi Tensor một cách hiệu quả khi độ chính xác là fp16 và kích thước đầu vào/đầu ra chia hết cho 8 hoặc 16 (đối với int8).
Lưu ý rằng với cuDNN v7.6.3 trở lên, các kích thước tích chập sẽ tự động được đệm khi cần thiết để tận dụng Lõi Tensor.
Hãy làm theo các phương pháp hay nhất bên dưới để tối đa hóa lợi ích hiệu suất của độ chính xác fp16
.
1. Sử dụng kernel fp16 tối ưu
Khi bật fp16
, hạt nhân nhân ma trận (GEMM) trong chương trình của bạn sẽ sử dụng phiên bản fp16
tương ứng sử dụng Lõi Tensor. Tuy nhiên, trong một số trường hợp, điều này không xảy ra và bạn không nhận được sự tăng tốc như mong đợi khi bật fp16
, vì thay vào đó, chương trình của bạn lại rơi vào tình trạng triển khai kém hiệu quả.
Trang thống kê hạt nhân GPU hiển thị các hoạt động nào đủ điều kiện cho Tensor Core và hạt nhân nào thực sự đang sử dụng Tensor Core hiệu quả. Hướng dẫn NVIDIA® về hiệu suất học sâu bao gồm các đề xuất bổ sung về cách tận dụng Lõi Tensor. Ngoài ra, lợi ích của việc sử dụng fp16
cũng sẽ hiển thị trong các hạt nhân trước đây bị giới hạn bộ nhớ, vì bây giờ các hoạt động sẽ mất một nửa thời gian.
2. Chia tỷ lệ tổn thất động và tĩnh
Cần phải chia tỷ lệ tổn thất khi sử dụng fp16
để ngăn chặn tình trạng tràn do độ chính xác thấp. Có hai loại tỷ lệ tổn thất, động và tĩnh, cả hai đều được giải thích chi tiết hơn trong hướng dẫn Độ chính xác hỗn hợp . Bạn có thể sử dụng chính mixed_float16
để tự động kích hoạt tính năng chia tỷ lệ tổn thất trong trình tối ưu hóa Keras.
Khi cố gắng tối ưu hóa hiệu suất, điều quan trọng cần nhớ là chia tỷ lệ tổn thất động có thể đưa ra các hoạt động có điều kiện bổ sung chạy trên máy chủ và dẫn đến các khoảng trống sẽ hiển thị giữa các bước trong trình xem theo dõi. Mặt khác, chia tỷ lệ tổn thất tĩnh không có chi phí chung như vậy và có thể là một lựa chọn tốt hơn về mặt hiệu suất với yêu cầu bạn cần chỉ định giá trị tỷ lệ tổn thất tĩnh chính xác.
2. Kích hoạt XLA bằng tf.function(jit_compile=True) hoặc tự động phân cụm
Bước cuối cùng để đạt được hiệu suất tốt nhất với một GPU duy nhất, bạn có thể thử nghiệm bật XLA, điều này sẽ hợp nhất các hoạt động và dẫn đến việc sử dụng thiết bị tốt hơn cũng như giảm mức tiêu tốn bộ nhớ. Để biết chi tiết về cách bật XLA trong chương trình của bạn bằng tf.function(jit_compile=True)
hoặc tự động phân cụm, hãy tham khảo hướng dẫn XLA .
Bạn có thể đặt mức JIT toàn cầu thành -1
(tắt), 1
hoặc 2
. Mức cao hơn sẽ hung hãn hơn và có thể làm giảm tính song song cũng như sử dụng nhiều bộ nhớ hơn. Đặt giá trị thành 1
nếu bạn bị hạn chế về bộ nhớ. Lưu ý rằng XLA không hoạt động tốt đối với các mô hình có hình dạng tenxơ đầu vào thay đổi vì trình biên dịch XLA sẽ phải tiếp tục biên dịch hạt nhân bất cứ khi nào nó gặp hình dạng mới.
2. Tối ưu hóa hiệu suất trên máy chủ đơn đa GPU
API tf.distribute.MirroredStrategy
có thể được sử dụng để mở rộng quy mô đào tạo mô hình từ một GPU sang nhiều GPU trên một máy chủ. (Để tìm hiểu thêm về cách thực hiện đào tạo phân tán với TensorFlow, hãy tham khảo khóa đào tạo Phân tán với TensorFlow , Sử dụng GPU và hướng dẫn Sử dụng TPU cũng như hướng dẫn Đào tạo phân tán với Keras .)
Mặc dù việc chuyển đổi từ một GPU sang nhiều GPU lý tưởng nhất là có thể mở rộng ngay lập tức nhưng đôi khi bạn có thể gặp phải các vấn đề về hiệu suất.
Khi chuyển từ đào tạo với một GPU sang nhiều GPU trên cùng một máy chủ, lý tưởng nhất là bạn nên trải nghiệm khả năng mở rộng hiệu suất chỉ với chi phí bổ sung của giao tiếp gradient và mức sử dụng luồng máy chủ tăng lên. Do chi phí này, bạn sẽ không thể tăng tốc chính xác gấp 2 lần nếu bạn chuyển từ 1 sang 2 GPU chẳng hạn.
Chế độ xem theo dõi bên dưới hiển thị ví dụ về chi phí liên lạc bổ sung khi đào tạo trên nhiều GPU. Có một số chi phí cần thực hiện để ghép các gradient, truyền đạt chúng qua các bản sao và phân chia chúng trước khi thực hiện cập nhật trọng số.
Danh sách kiểm tra sau đây sẽ giúp bạn đạt được hiệu suất tốt hơn khi tối ưu hóa hiệu suất trong trường hợp nhiều GPU:
- Cố gắng tối đa hóa kích thước lô, điều này sẽ dẫn đến việc sử dụng thiết bị cao hơn và giảm dần chi phí liên lạc trên nhiều GPU. Việc sử dụng trình lược tả bộ nhớ sẽ giúp bạn biết được mức độ sử dụng bộ nhớ tối đa của chương trình của bạn. Lưu ý rằng mặc dù kích thước lô cao hơn có thể ảnh hưởng đến sự hội tụ, nhưng điều này thường bị ảnh hưởng nhiều hơn bởi các lợi ích về hiệu suất.
- Khi chuyển từ một GPU sang nhiều GPU, cùng một máy chủ hiện phải xử lý nhiều dữ liệu đầu vào hơn. Vì vậy, sau (1), nên kiểm tra lại hiệu suất của đường ống đầu vào và đảm bảo rằng nó không bị tắc nghẽn.
- Kiểm tra dòng thời gian GPU trong chế độ xem theo dõi chương trình của bạn để biết bất kỳ lệnh gọi AllReduce không cần thiết nào vì điều này dẫn đến đồng bộ hóa trên tất cả các thiết bị. Trong chế độ xem theo dõi được hiển thị ở trên, AllReduce được thực hiện thông qua hạt nhân NCCL và chỉ có một lệnh gọi NCCL trên mỗi GPU cho độ dốc trên mỗi bước.
- Kiểm tra các hoạt động sao chép D2H, H2D và D2D không cần thiết có thể được giảm thiểu.
- Kiểm tra thời gian của từng bước để đảm bảo mỗi bản sao đều thực hiện cùng một công việc. Ví dụ: có thể xảy ra trường hợp một GPU (thường là
GPU0
) được đăng ký quá mức do máy chủ nhầm lẫn đã đặt nhiều công việc hơn vào nó. - Cuối cùng, hãy kiểm tra bước đào tạo trên tất cả các GPU trong chế độ xem theo dõi của bạn để biết bất kỳ hoạt động nào đang thực hiện tuần tự. Điều này thường xảy ra khi chương trình của bạn bao gồm các phần phụ thuộc điều khiển từ GPU này sang GPU khác. Trước đây, việc gỡ lỗi hiệu suất trong tình huống này đã được giải quyết theo từng trường hợp cụ thể. Nếu bạn quan sát thấy hành vi này trong chương trình của mình, hãy gửi vấn đề GitHub kèm theo hình ảnh của chế độ xem theo dõi của bạn.
1. Tối ưu hóa độ dốc AllReduce
Khi đào tạo với chiến lược đồng bộ, mỗi thiết bị sẽ nhận được một phần dữ liệu đầu vào.
Sau khi tính toán các lượt tiến và lùi qua mô hình, độ dốc tính toán trên mỗi thiết bị cần được tổng hợp và giảm bớt. Độ dốc AllReduce này xảy ra sau khi tính toán độ dốc trên từng thiết bị và trước khi trình tối ưu hóa cập nhật trọng số mô hình.
Trước tiên, mỗi GPU nối các gradient trên các lớp mô hình, truyền chúng qua các GPU bằng cách sử dụng tf.distribute.CrossDeviceOps
( tf.distribute.NcclAllReduce
là mặc định), sau đó trả về các gradient sau khi giảm trên mỗi lớp.
Trình tối ưu hóa sẽ sử dụng các độ dốc đã giảm này để cập nhật trọng số cho mô hình của bạn. Lý tưởng nhất là quá trình này nên diễn ra cùng lúc trên tất cả các GPU để tránh mọi chi phí phát sinh.
Thời gian để AllReduce sẽ xấp xỉ như sau:
(number of parameters * 4bytes)/ (communication bandwidth)
Tính toán này hữu ích như một cách kiểm tra nhanh để hiểu liệu hiệu suất bạn đạt được khi chạy công việc đào tạo phân tán có như mong đợi hay không hoặc liệu bạn có cần thực hiện thêm việc gỡ lỗi hiệu suất hay không. Bạn có thể lấy số lượng tham số trong mô hình của mình từ Model.summary
.
Lưu ý rằng mỗi tham số mô hình có kích thước 4 byte vì TensorFlow sử dụng fp32
(float32) để truyền đạt độ dốc. Ngay cả khi bạn đã bật fp16
, NCCL AllReduce vẫn sử dụng các tham số fp32
.
Để có được lợi ích của việc mở rộng quy mô, thời gian thực hiện các bước cần phải cao hơn nhiều so với các chi phí chung này. Một cách để đạt được điều này là sử dụng kích thước lô cao hơn vì kích thước lô ảnh hưởng đến thời gian của bước nhưng không ảnh hưởng đến chi phí liên lạc.
2. Tranh chấp luồng máy chủ GPU
Khi chạy nhiều GPU, công việc của CPU là giữ cho tất cả các thiết bị luôn bận rộn bằng cách khởi chạy các nhân GPU trên các thiết bị một cách hiệu quả.
Tuy nhiên, khi có nhiều hoạt động độc lập mà CPU có thể lên lịch trên một GPU, CPU có thể quyết định sử dụng nhiều luồng máy chủ của nó để giữ cho một GPU bận, sau đó khởi chạy các hạt nhân trên một GPU khác theo thứ tự không xác định. . Điều này có thể gây ra tỷ lệ sai lệch hoặc âm, có thể ảnh hưởng tiêu cực đến hiệu suất.
Trình xem theo dõi bên dưới hiển thị chi phí hoạt động khi CPU trì trệ Nhân GPU khởi chạy không hiệu quả, vì GPU1
không hoạt động và sau đó bắt đầu chạy các hoạt động sau khi GPU2
khởi động.
Chế độ xem theo dõi của máy chủ cho thấy máy chủ đang khởi chạy hạt nhân trên GPU2
trước khi khởi chạy chúng trên GPU1
(lưu ý rằng các hoạt động tf_Compute*
bên dưới không biểu thị các luồng CPU).
Nếu bạn gặp phải tình trạng hạt nhân GPU đáng kinh ngạc này trong chế độ xem theo dõi của chương trình thì hành động được đề xuất là:
- Đặt biến môi trường TensorFlow
TF_GPU_THREAD_MODE
thànhgpu_private
. Biến môi trường này sẽ yêu cầu máy chủ giữ các luồng cho GPU ở chế độ riêng tư. - Theo mặc định,
TF_GPU_THREAD_MODE=gpu_private
đặt số lượng luồng thành 2, đủ trong hầu hết các trường hợp. Tuy nhiên, con số đó có thể được thay đổi bằng cách đặt biến môi trường TensorFlowTF_GPU_THREAD_COUNT
thành số lượng luồng mong muốn.