Tối ưu hóa hiệu suất GPU TensorFlow với TensorFlow Profiler

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à có được hiệu suất tối đa từ GPU của bạn, đồng thời gỡ lỗi khi một hoặc nhiều GPU của bạn chưa được sử dụng hết.

Nếu bạn chưa quen với Hồ sơ:

Hãy nhớ rằng việc giảm tải các tính toán sang GPU có thể không phải lúc nào cũng có lợi, đặc biệt là đối với các mô hình nhỏ. Có thể có chi phí 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 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 gỡ lỗi các vấn đề về hiệu suất bắt đầu với một GPU, sau đó chuyển sang một máy chủ duy nhất có nhiều GPU.

Bạn nên gỡ lỗi các vấn đề về hiệu suất theo thứ tự sau:

  1. Tối ưu hóa và gỡ lỗi hiệu suất trên một GPU:
    1. Kiểm tra xem đường ống đầu vào có bị tắc nghẽn hay không.
    2. Gỡ lỗi hiệu suất của một GPU.
    3. Bật độ chính xác hỗn hợp (với fp16 (float16)) và tùy chọn bật XLA .
  2. 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 để đào tạo mô hình trên một máy chủ duy nhất có nhiều GPU và nhận thấy việ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 đa 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 đang sử dụng tf.function . function. Các API Model.compileModel.fit sẽ tự động sử dụng chức năng tf.function . Khi viết một vòng lặp đào tạo tùy chỉnh với tf.GradientTape , hãy tham khảo Hiệu suất tốt hơn với tf. Chức năng để biết cách bật tf.function . Chức năng s.

Các phần tiếp theo thảo luận về các phương pháp tiếp cận được đề xuất cho từng tình huống ở trên để giúp xác định và khắc phục các nút thắt 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ó hiệu suất sử dụng GPU cao, tối thiểu giao tiếp giữa CPU (máy chủ) với GPU (thiết bị) 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 một cấu hình cho một mô hình chạy với một GPU.

Trang tổng quan về Hồ sơ của TensorBoard — trang này 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 chương trình của bạn so với kịch bản lý tưởng.

TensorFlow Profiler Overview Page

Các con số chính cần chú ý trên trang tổng quan là:

  1. Thời gian của từng bước là bao nhiêu từ khi thực thi thiết bị thực tế
  2. Phần trăm hoạt động được đặt trên thiết bị so với máy chủ
  3. Có bao nhiêu hạt nhân 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 sẽ cần phải làm quen với trình xem theo dõi Hồ sơ của TensorBoard. Các phần bên dưới hiển thị một số mẫu trình xem theo dõi phổ biến mà bạn nên tìm khi chẩn đoán tắc nghẽn hiệu suất.

Dưới đây là hình ảnh của chế độ xem theo dõi mô hình chạy trên một GPU. Từ các phần TensorFlow Name ScopeTensorFlow 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 mát, tính toán chuyển tiếp / gradient 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ể có các hoạt động chạy trên GPU bên cạnh mỗi Luồng , tham chiếu đế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, Luồng # 118 được sử dụng để khởi chạy hạt nhân tính toán và các bản sao từ thiết bị đến thiết bị. Luồng # 119 được sử dụng để sao chép từ máy chủ sang thiết bị và Luồng # 120 dành cho thiết bị để sao chép trên 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 biểu diễn.

image

Ví dụ: dòng thời gian tính toán GPU ( Luồng # 118 ) trông "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ị sang máy chủ ( Luồng # 120 ), cũng như khoảng cách tối thiểu giữa các bước. Khi bạn chạy Hồ sơ 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 gỡ lỗi hiệu suất GPU là xác định xem chương trình của bạn có bị ràng buộc đầu vào hay không. Cách dễ nhất để tìm ra điều này là sử dụng trình phân tích đường ống đầu vào của Profiler, trên TensorBoard, cung cấp tổng quan về thời gian dành cho đường ống đầu vào.

image

Bạn có thể thực hiện các hành động tiềm năng sau nếu quy trình đầu vào của bạn đóng góp đáng kể vào thời gian từng 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ó phải là nút cổ chai 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 bất kỳ xử lý trước nào. Đây là một ví dụ về việc sử dụng kỹ thuật này cho một mô hình ResNet. Nếu đường dẫn đầu vào là 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 tổng hợp / ngẫu nhiên được tạo. Chi phí duy nhất trong trường hợp dữ liệu tổng hợp sẽ là do bản sao dữ liệu đầu vào 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 ố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 nhìn vào trình xem theo dõi 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 nhận xé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 các bước 8 và 9, có nghĩa là GPU không hoạt động trong thời gian đó.

image

Nếu trình xem theo dõi 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ị ràng buộc đầu vào. Trong trường hợp đó, bạn nên tham khảo phần trước về 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 một đường dẫn đầu vào được tối ưu hóa, bạn vẫn có thể có khoảng cách 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 tranh chấp luồng CPU. tf.data sử dụng các luồng nền để xử lý đường ống song song. Các luồng này có thể ảnh hưởng đến hoạt động phía máy chủ của 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 khoảng trống lớn ở phía máy chủ, nơi lên lịch 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 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 hoạt động bên ngoài chức năng tf.function . chạy trên máy chủ. Các hoạt động này không có hiệu suất tốt bằng các hoạt động bên trong biểu đồ TensorFlow. Ngoài ra, một số hoạt động này chạy trên CPU và sao chép bộ căng qua lại từ GPU.

Nếu sau khi tối ưu hóa quy trình đầu vào, bạn vẫn nhận thấy khoảng trống giữa các bước trong trình xem theo dõi, 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 / chỉ số có cải thiện hiệu suất hay không. Một số chi tiết của 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í 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 pháp compile trong API tf.keras , việc đặt cờ experimental_steps_per_execution nghiệm_các_thực_hiện sẽ tự động thực hiện việc này. Đối với các vòng huấn luyện tùy chỉnh, hãy sử dụng tf. tf.while_loop .

2. Đạt được mức sử dụng thiết bị cao hơn

1. Nhân GPU nhỏ và sự chậm trễ khởi chạy nhân máy chủ

Máy chủ sắp xếp các nhân để chạy trên GPU, nhưng có độ trễ (khoảng 20-40 μs) trước khi các 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 hàng đợi đủ số nhân trên GPU sao cho GPU dành phần lớn thời gian để thực thi, thay vì đợi máy chủ xếp hàng thêm nhiều nhân.

Trang tổng quan của Profiler trên TensorBoard hiển thị thời gian GPU không hoạt động do chờ máy chủ khởi chạy các hạt nhân. Trong hình ảnh bên dưới, GPU ở chế độ không hoạt động trong khoảng 10% thời gian của bước chờ khởi chạy các hạt nhân.

image

Trình xem theo dõi cho cùng một chương trình này hiển thị các khoảng trống nhỏ giữa các nhân khi máy chủ đang bận khởi chạy các nhân trên GPU.

image

Bằng cách khởi chạy nhiều hoạt động nhỏ trên GPU (ví dụ như một bổ sung 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 hoạt động 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ỏ (cùng khoảng thời gian với độ trễ khởi chạy) và có thể dẫn đến sự chậm trễ khởi chạy hạt nhân máy chủ.

image

Nếu trình xem theo dõi 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ể:

  • Kết hợp các bộ căng nhỏ và sử dụng các hoạt động được vectơ 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 nhiều công việc hơn, điều này sẽ giữ cho GPU bận rộn lâu hơn.
  • Đảm bảo rằng bạn đang sử dụng tf.function để tạo đồ thị 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ụng Model.fit (trái ngược với vòng lặp đào tạo tùy chỉnh với tf.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 chuyển đến 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 để có 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í op TensorFlow

Trang tổng quan về Hồ sơ hiển thị cho bạn tỷ lệ phần trăm hoạt động được đặt trên máy chủ lưu trữ 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 nhìn vào trình xem theo dõi . Giống như trong hình ảnh bên dưới, bạn muốn tỷ lệ hoạt động trên máy chủ lưu trữ rất nhỏ so với thiết bị.

image

Tốt nhất, hầu hết các hoạt động tính toán chuyên sâu nên được đặt trên GPU.

Để biết các hoạt động và bộ căng 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, 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í xác định hơn của các hoạt động trên thiết bị của bạn.

Một lý do để phần lớn các hoạt động được đặt trên GPU là để ngăn các bản sao bộ nhớ quá mức giữa máy chủ và thiết bị (dự kiến ​​sẽ có cá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 minh họa trong chế độ xem theo dõi bên dưới trên các luồng GPU # 167 , # 168# 169 .

image

Những bản sao này đôi khi có thể làm ảnh hưởng đến hiệu suất nếu chúng chặn không cho nhân GPU thực thi. Các hoạt động sao chép bộ nhớ trong trình xem theo dõi có thêm thông tin về các hoạt động là nguồn gốc của các tensor được sao chép này, nhưng có thể không phải lúc nào cũng dễ dàng liên kết memCopy với op. Trong những trường hợp này, sẽ rất hữu ích khi xem các mục gần đó để kiểm tra xem bản sao bộ nhớ có xảy ra ở cùng một vị trí trong mỗi bước hay không.

3. Nhân hiệu quả hơn trên GPU

Khi việc sử dụng GPU của chương trình của bạn có thể chấp nhận được, bước tiếp theo là xem xét việc tăng hiệu quả của các nhân GPU bằng cách sử dụng Lõi Tensor hoặc hoạt động hợp nhất.

1. Sử dụng lõi Tensor

Các GPU NVIDIA® hiện đại có Lõi Tensor chuyên biệt 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ê về nhân GPU của TensorBoard để trực quan hóa những nhân GPU nào đủ điều kiện cho Tensor Core và những nhân nào đang sử dụng Tensor Core. Bật fp16 (xem phần Bật chính xác hỗn hợp bên dưới) là một cách để làm cho các hạt nhân Ma trận Chung (GEMM) trong chương trình của bạn (hoạt động matmul) 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 hiệu quả cho GPU, hãy tham khảo hướng dẫn về hiệu suất học sâu NVIDIA® .

2. Cầu chì hoạt động

Sử dụng tf.function(jit_compile=True) để kết hợp 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 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. Bật độ chính xác hỗn hợp

Hướng dẫn về độ chính xác hỗn hợp TensorFlow cho biết cách bật độ chính xác fp16 trên GPU. Bật AMP trên GPU NVIDIA® để sử dụng Lõi Tensor và tăng tốc độ tổng thể lên đến 3 lần khi so sánh với việc chỉ sử dụng độ chính xác fp32 (float32) trên kiến ​​trúc GPU Volta và 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 các hạt nhân sử dụng Tensor Cores. Nhân GPU sử dụng lõi Tensor 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, 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.

Thực hiện theo các phương pháp hay nhất dưới đây để tối đa hóa lợi ích hiệu suất của độ chính xác fp16 .

1. Sử dụng hạt nhân fp16 tối ưu

Với fp16 được bật, các 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 các 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 gặp phải tình trạng tăng tốc như mong đợi khi bật fp16 , vì chương trình của bạn rơi vào tình trạng triển khai không hiệu quả.

image

Trang thống kê về nhân GPU cho biết các hoạt động nào đủ điều kiện cho Tensor Core và những 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 chứa 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. Tỷ lệ tổn thất động so với tĩnh

Chia tỷ lệ tổn thất là cần thiết khi sử dụng fp16 để ngăn chặn dòng chảy dưới 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 sách mixed_float16 để tự động bậ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ể tạo ra các hoạt động có điều kiện bổ sung chạy trên máy chủ và dẫn đến 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, tỷ lệ tổn thất tĩnh không có các chi phí 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 việc bạn cần chỉ định giá trị tỷ lệ tổn thất tĩnh chính xác.

2. Bật XLA với 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 với việc 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 và sử dụng bộ nhớ thấp hơn. Để biết chi tiết về cách bật XLA trong chương trình của bạn với 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 chung thành -1 (tắt), 1 hoặc 2 . Mức độ cao hơn sẽ tích cực hơn và có thể làm giảm tính song song và sử dụng nhiều bộ nhớ hơn. Đặt giá trị thành 1 nếu bạn bị giới hạn 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 tensor đầu vào thay đổi vì trình biên dịch XLA sẽ phải tiếp tục biên dịch các hạt nhân bất cứ khi nào nó gặp các 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ủ duy nhất. (Để 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 Hướng dẫn đào tạo phân tán với TensorFlow , Sử dụng GPUSử dụng TPU và 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 nên có thể mở rộng ra khỏi hộp, 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 việc mở rộng hiệu suất chỉ với chi phí bổ sung của giao tiếp gradient và tăng mức sử dụng luồng máy chủ. Vì chi phí này, bạn sẽ không có tốc độ tăng gấp 2 lần chính xác nếu bạn di chuyển từ 1 đến 2 GPU.

Chế độ xem theo dõi bên dưới cho thấy một ví dụ về chi phí giao tiếp bổ sung khi đào tạo trên nhiều GPU. Có một số chi phí để nối các gradient, giao tiếp chúng qua các bản sao và chia nhỏ chúng trước khi thực hiện cập nhật trọng lượng.

image

Danh sách kiểm tra sau 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 kịch bản đa GPU:

  1. 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à phân bổ chi phí giao tiếp trên nhiều GPU. Sử dụng trình biên dịch bộ nhớ giúp hiểu được chương trình của bạn đã gần đến mức sử dụng bộ nhớ cao nhất như thế nào. 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 vượt trội hơn so với lợi ích về hiệu suất.
  2. Khi chuyển từ một GPU sang nhiều GPU, cùng một máy chủ lưu trữ giờ đây 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 hoạt động của đường ống đầu vào và đảm bảo rằng nó không bị tắc nghẽn.
  3. Kiểm tra dòng thời gian GPU trong chế độ xem theo dõi chương trình của bạn để tìm 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 các gradient trên mỗi bước.
  4. 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.
  5. Kiểm tra thời gian của từng bước để đảm bảo mỗi bản sao đang thực hiện công việc giống nhau. Ví dụ: có thể xảy ra trường hợp một GPU (thường là GPU0 ) bị đăng ký quá mức do máy chủ lưu trữ nhầm lẫn khi đưa thêm công việc lên đó.
  6. 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ụ 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. 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 sự cố GitHub với hình ảnh về chế độ xem theo dõi của bạn.

1. Tối ưu hóa gradient AllReduce

Khi đào tạo với chiến lược đồng bộ, mỗi thiết bị nhận được một phần dữ liệu đầu vào.

Sau khi tính toán chuyển tiếp và chuyển tiếp qua mô hình, các gradient được tính toán trên mỗi thiết bị cần được tổng hợp và giảm bớt. AllReduce gradient này xảy ra sau khi tính toán gradient trên mỗi thiết bị và trước khi trình tối ưu hóa cập nhật trọng số của mô hình.

Trước tiên, mỗi GPU sẽ nối các gradient trên các lớp mô hình, giao tiếp chúng giữa các GPU bằng tf.distribute.CrossDeviceOps ( tf.distribute.NcclAllReduce là mặc định), sau đó trả về các gradient sau khi giảm mỗi lớp.

Trình tối ưu hóa sẽ sử dụng các gradient đã giảm này để cập nhật trọng lượng của mô hình của bạn. Tốt nhất, quá trình này nên diễn ra cùng một lúc trên tất cả các GPU để ngăn chặn bất kỳ chi phí nào.

Thời gian để Giảm tất cả phải tương đương với:

(number of parameters * 4bytes)/ (communication bandwidth)

Tính toán này hữu ích như một kiểm tra nhanh để hiểu xem hiệu suất bạn có khi chạy công việc đào tạo phân tán có như mong đợi hay bạn cần thực hiện thêm gỡ lỗi hiệu suất. 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) để giao tiếp các gradient. Ngay cả khi bạn đã bật fp16 , NCCL AllReduce vẫn sử dụng các tham số fp32 .

Để có được những lợi ích của việc mở rộng quy mô, thời gian bước cần phải cao hơn nhiều so với những 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í giao tiếp.

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ị bận rộn bằng cách khởi chạy hiệu quả các nhân GPU trên các thiết bị.

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, thì CPU có thể quyết định sử dụng nhiều luồng máy chủ của nó để giữ cho một GPU luôn bận rộn và sau đó khởi chạy các nhân trên GPU khác theo một thứ tự không xác định. . Điều này có thể gây ra lệch tỷ lệ hoặc tỷ lệ â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í 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.

image

Chế độ xem theo dõi cho máy chủ cho thấy máy chủ đang khởi chạy các 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 tf_Compute* bên dưới không phải là chỉ ra các luồng CPU).

image

Nếu bạn gặp phải loại nhân GPU đáng kinh ngạc này trong chế độ xem theo dõi chương trình của mình, thì hành động được đề xuất là:

  • Đặt biến môi trường TensorFlow TF_GPU_THREAD_MODE thành gpu_private . Biến môi trường này sẽ yêu cầu máy chủ lưu trữ các luồng riêng tư cho GPU.
  • Theo mặc định, TF_GPU_THREAD_MODE=gpu_private đặt số luồng thành 2, là đủ trong hầu hết các trường hợp. Tuy nhiên, số đó có thể được thay đổi bằng cách đặt biến môi trường TensorFlow TF_GPU_THREAD_COUNT thành số luồng mong muốn.