Trang này được dịch bởi Cloud Translation API.
Switch to English

Phân tích hiệu suất tf.data với TF Profiler

Tổng quat

Hướng dẫn này giả định quen thuộc với TensorFlow Profilertf.data . Nó nhằm mục đích cung cấp các hướng dẫn từng bước với các ví dụ để giúp người dùng chẩn đoán và khắc phục các sự cố về hiệu suất của đường ống đầu vào.

Để bắt đầu, hãy thu thập một hồ sơ về công việc TensorFlow của bạn. Hướng dẫn về cách làm như vậy có sẵn cho CPU / GPUCloud TPU .

TensorFlow Trace Viewer

Quy trình phân tích chi tiết dưới đây tập trung vào công cụ theo dõi trong Profiler. Công cụ này hiển thị dòng thời gian hiển thị thời lượng của các op được thực hiện bởi chương trình TensorFlow của bạn và cho phép bạn xác định các op nào mất nhiều thời gian nhất để thực thi. Để biết thêm thông tin về trình xem theo dõi, hãy xem phần này của hướng dẫn TF Profiler. Nói chung, các sự kiện tf.data sẽ xuất hiện trên dòng thời gian CPU chủ.

Phân tích công việc

Hãy làm theo quy trình làm việc dưới đây. Nếu bạn có phản hồi để giúp chúng tôi cải thiện nó, vui lòng tạo một vấn đề github với nhãn mác comp: data.

1. Đường ống tf.data của bạn có tạo ra dữ liệu đủ nhanh không?

Bắt đầu bằng cách xác định xem liệu đường ống đầu vào có phải là nút cổ chai cho chương trình TensorFlow của bạn hay không.

Để làm như vậy, hãy tìm IteratorGetNext::DoCompute ops trong trình xem theo dõi. Nói chung, bạn mong đợi để nhìn thấy những điều này khi bắt đầu một bước. Các lát cắt này biểu thị thời gian cần thiết cho đường ống đầu vào của bạn để mang lại một loạt các yếu tố khi được yêu cầu. Nếu bạn đang sử dụng máy ảnh hoặc lặp qua tập dữ liệu của mình trong một tf.function , chúng sẽ được tìm thấy trong các chủ đề tf_data_iterator_get_next .

Lưu ý rằng nếu bạn đang sử dụng chiến lược phân phối , bạn có thể thấy các sự kiện IteratorGetNextAsOptional::DoCompute thay vì IteratorGetNext::DoCompute (kể từ TF 2.3).

image

Nếu các cuộc gọi trở lại nhanh chóng (<= 50 chúng tôi), điều này có nghĩa là dữ liệu của bạn có sẵn khi được yêu cầu. Các đường ống đầu vào không phải là nút cổ chai của bạn; xem hướng dẫn Profiler để biết các mẹo phân tích hiệu suất chung hơn.

image

Nếu các cuộc gọi trở lại chậm, tf.data không thể theo kịp các yêu cầu của người tiêu dùng. Tiếp tục đến phần tiếp theo.

2. Bạn đang tìm nạp dữ liệu?

Cách thực hành tốt nhất cho hiệu suất đường ống đầu vào là chèn một phép biến đổi tf.data.Dataset.prefetch ở cuối đường ống tf.data của bạn. Chuyển đổi này chồng lấp tính toán tiền xử lý của đường ống đầu vào với bước tiếp theo của tính toán mô hình và được yêu cầu cho hiệu suất đường ống đầu vào tối ưu khi đào tạo mô hình của bạn. Nếu bạn đang tìm nạp dữ liệu, bạn sẽ thấy một lát Iterator::Prefetch trên cùng một luồng với IteratorGetNext::DoCompute op.

image

Nếu bạn không có prefetch ở cuối đường ống , bạn nên thêm một. Để biết thêm thông tin về các đề xuất hiệu suất tf.data , hãy xem hướng dẫn hiệu suất tf.data .

Nếu bạn đã tìm nạp trước dữ liệu và đường ống đầu vào vẫn là nút cổ chai của bạn, hãy tiếp tục đến phần tiếp theo để phân tích hiệu suất.

3. Bạn có đạt được mức sử dụng CPU cao không?

tf.data đạt được thông lượng cao bằng cách cố gắng sử dụng tốt nhất các tài nguyên có sẵn. Nói chung, ngay cả khi chạy mô hình của bạn trên máy gia tốc như GPU hoặc TPU, các đường ống tf.data vẫn chạy trên CPU. Bạn có thể kiểm tra mức độ sử dụng của mình bằng các công cụ như sarhtop hoặc trong bảng điều khiển giám sát đám mây nếu bạn đang chạy trên GCP.

Nếu mức độ sử dụng của bạn thấp, điều này cho thấy đường ống đầu vào của bạn có thể không tận dụng được tối đa CPU chủ. Bạn nên tham khảo hướng dẫn hiệu suất tf.data để biết cách thực hành tốt nhất. Nếu bạn đã áp dụng các thực tiễn tốt nhất và việc sử dụng và thông lượng vẫn còn thấp, hãy tiếp tục phân tích nút cổ chai bên dưới.

Nếu việc sử dụng của bạn đang tiến gần đến giới hạn tài nguyên , để cải thiện hiệu suất hơn nữa, bạn cần cải thiện hiệu quả của đường ống đầu vào (ví dụ: tránh tính toán không cần thiết) hoặc giảm tải tính toán.

Bạn có thể cải thiện hiệu quả của đường ống đầu vào bằng cách tránh tính toán không cần thiết trong tf.data . Một cách để làm điều này là chèn một phép biến đổi tf.data.Dataset.cache sau khi làm việc chuyên sâu tính toán nếu dữ liệu của bạn vừa với bộ nhớ; điều này làm giảm tính toán với chi phí sử dụng bộ nhớ tăng. Ngoài ra, vô hiệu hóa song song nội bộ trong tf.data có khả năng tăng hiệu quả lên> 10% và có thể được thực hiện bằng cách đặt tùy chọn sau trên đường ống đầu vào của bạn:

 dataset = ...
options = tf.data.Options()
options.experimental_threading.max_intra_op_parallelism = 1
dataset = dataset.with_options(options)
 

4. Phân tích nút cổ chai

Phần sau đây hướng dẫn cách đọc các sự kiện tf.data trong trình xem theo dõi để hiểu nút thắt ở đâu và các chiến lược giảm thiểu có thể.

Hiểu các sự kiện tf.data trong Profiler

Mỗi sự kiện tf.data trong Profiler có tên Iterator::<Dataset> , trong đó <Dataset> là tên của nguồn dữ liệu hoặc chuyển đổi. Mỗi sự kiện cũng có tên dài Iterator::<Dataset_1>::...::<Dataset_n> , mà bạn có thể thấy bằng cách nhấp vào sự kiện tf.data . Trong tên dài, <Dataset_n> khớp với <Dataset> từ tên (ngắn) và các bộ dữ liệu khác trong tên dài biểu thị các phép biến đổi xuôi dòng.

image

Ví dụ: ảnh chụp màn hình ở trên được tạo từ mã sau:

 dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)
 

Ở đây, sự kiện Iterator::Map có tên dài Iterator::BatchV2::FiniteRepeat::Map . Lưu ý rằng tên bộ dữ liệu có thể hơi khác với API python (ví dụ: FiniteRepeat thay vì Lặp lại), nhưng phải đủ trực quan để phân tích cú pháp.

Biến đổi đồng bộ và không đồng bộ

Đối với các phép biến đổi tf.data đồng bộ (như BatchMap ), bạn sẽ thấy các sự kiện từ các phép biến đổi ngược dòng trên cùng một luồng. Trong ví dụ trên, vì tất cả các phép biến đổi được sử dụng là đồng bộ, tất cả các sự kiện xuất hiện trên cùng một luồng.

Đối với các phép biến đổi không đồng bộ (như Prefetch , ParallelMap , ParallelInterleaveMapAndBatch ) từ các phép biến đổi ngược dòng sẽ nằm trên một luồng khác. Trong những trường hợp như vậy, tên lâu dài, có thể giúp bạn xác định chuyển đổi nào trong đường ống mà một sự kiện tương ứng.

image

Ví dụ: ảnh chụp màn hình ở trên được tạo từ mã sau:

 dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)
dataset = dataset.prefetch(1)
 

Ở đây, các sự kiện Iterator::Prefetch nằm trên các tf_data_iterator_get_next . Do Prefetch không đồng bộ, các sự kiện đầu vào của nó ( BatchV2 ) sẽ nằm trên một luồng khác và có thể được tìm thấy bằng cách tìm kiếm tên dài Iterator::Prefetch::BatchV2 . Trong trường hợp này, chúng nằm trên luồng tf_data_iterator_resource . Từ tên dài của nó, bạn có thể suy luận rằng BatchV2 là thượng nguồn của Prefetch . Hơn nữa, parent_id của sự kiện BatchV2 sẽ khớp với ID của sự kiện Prefetch .

Xác định nút cổ chai

Nói chung, để xác định nút cổ chai trong đường ống đầu vào của bạn, hãy đi bộ đường ống đầu vào từ biến đổi ngoài cùng cho đến nguồn. Bắt đầu từ biến đổi cuối cùng trong đường ống của bạn, tái diễn thành các biến đổi ngược dòng cho đến khi bạn tìm thấy một biến đổi chậm hoặc đạt đến một tập dữ liệu nguồn, chẳng hạn như TFRecord . Trong ví dụ trên, bạn sẽ bắt đầu từ Prefetch , sau đó đi ngược dòng lên BatchV2 , FiniteRepeat , Map và cuối cùng là Range .

Nói chung, một phép biến đổi chậm tương ứng với một sự kiện có các sự kiện dài, nhưng các sự kiện đầu vào của nó là ngắn. Một số ví dụ sau đây.

Lưu ý rằng phép biến đổi cuối cùng (ngoài cùng) trong hầu hết các đường ống đầu vào máy chủ là sự kiện Iterator::Model . Chuyển đổi mô hình được giới thiệu tự động bởi thời gian chạy tf.data và được sử dụng để thiết bị và tự động hóa hiệu suất đường ống đầu vào.

Nếu công việc của bạn đang sử dụng chiến lược phân phối , trình xem theo dõi sẽ chứa các sự kiện bổ sung tương ứng với đường ống đầu vào của thiết bị. Biến đổi ngoài cùng của đường ống thiết bị (được lồng trong IteratorGetNextOp::DoCompute hoặc IteratorGetNextAsOptionalOp::DoCompute ) sẽ là một sự kiện Iterator::Prefetch với sự kiện Iterator::Generator ngược dòng. Bạn có thể tìm thấy đường dẫn máy chủ tương ứng bằng cách tìm kiếm các sự kiện Iterator::Model .

ví dụ 1

image

Ảnh chụp màn hình ở trên được tạo từ đường ống đầu vào sau:

 dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record)
dataset = dataset.batch(32)
dataset = dataset.repeat()
 

Trong ảnh chụp màn hình, hãy quan sát rằng (1) Iterator::Map sự kiện Iterator::Map dài, nhưng (2) các sự kiện đầu vào của nó ( Iterator::FlatMap ) trở lại nhanh chóng. Điều này cho thấy việc chuyển đổi Bản đồ tuần tự là nút cổ chai.

Lưu ý rằng trong ảnh chụp màn hình, sự kiện InstantiatedCapturedFunction::Run tương ứng với thời gian cần thiết để thực thi chức năng bản đồ.

Ví dụ 2

image

Ảnh chụp màn hình ở trên được tạo từ đường ống đầu vào sau:

 dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record, num_parallel_calls=2)
dataset = dataset.batch(32)
dataset = dataset.repeat()
 

Ví dụ này tương tự như trên, nhưng sử dụng ParallelMap thay vì Map. Ở đây chúng tôi nhận thấy rằng (1) các sự kiện Iterator::ParallelMap dài, nhưng (2) các sự kiện đầu vào của nó Iterator::FlatMap (nằm trên một luồng khác, vì ParallelMap không đồng bộ) là ngắn. Điều này cho thấy rằng phép chuyển đổi ParallelMap là nút cổ chai.

Giải quyết nút thắt

Bộ dữ liệu nguồn

Nếu bạn đã xác định nguồn dữ liệu là nút cổ chai, chẳng hạn như đọc từ các tệp TFRecord, bạn có thể cải thiện hiệu suất bằng cách song song trích xuất dữ liệu. Để làm như vậy, đảm bảo rằng dữ liệu của bạn được phân chia trên nhiều tệp và sử dụng tf.data.Dataset.interleave với tham số num_parallel_calls được đặt thành tf.data.experimental.AUTOTUNE . Nếu tính xác định không quan trọng đối với chương trình của bạn, bạn có thể cải thiện hiệu suất hơn nữa bằng cách đặt cờ deterministic=False trên tf.data.Dataset.interleave kể từ TF 2.2. Ví dụ: nếu bạn đang đọc từ TFRecords, bạn có thể làm như sau:

 dataset = tf.data.Dataset.from_tensor_slices(filenames)
dataset = dataset.interleave(tf.data.TFRecordDataset,
  num_parallel_calls=tf.data.experimental.AUTOTUNE,
  deterministic=False)
 

Lưu ý rằng các tệp được phân tách phải lớn một cách hợp lý để khấu hao chi phí mở tệp. Để biết thêm chi tiết về trích xuất dữ liệu song song, xem phần này của hướng dẫn hiệu suất tf.data .

Bộ dữ liệu chuyển đổi

Nếu bạn đã xác định một phép chuyển đổi tf.data trung gian là nút cổ chai, bạn có thể giải quyết nó bằng cách song song hóa phép chuyển đổi hoặc lưu vào bộ đệm tính toán nếu dữ liệu của bạn phù hợp với bộ nhớ và nó phù hợp. Một số biến đổi như Map có các bản sao song song; hướng dẫn hiệu suất tf.data thích cách song song hóa chúng. Các phép biến đổi khác, chẳng hạn như Filter , UnbatchBatch vốn là tuần tự; bạn có thể song song hóa chúng bằng cách giới thiệu song song bên ngoài. Ví dụ: giả sử đường ống đầu vào của bạn ban đầu trông giống như sau, với Batch là nút cổ chai:

 filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)
dataset = filenames_to_dataset(filenames)
dataset = dataset.batch(batch_size)
 

Bạn có thể giới thiệu song song bên ngoài, bằng cách chạy nhiều bản sao của đường ống đầu vào trên các đầu vào bị phân mảnh và kết hợp các kết quả:

 filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)

def make_dataset(shard_index):
  filenames = filenames.shard(NUM_SHARDS, shard_index)
  dataset = filenames_to_dataset(filenames)
  Return dataset.batch(batch_size)

indices = tf.data.Dataset.range(NUM_SHARDS)
dataset = indices.interleave(make_dataset,
                             num_parallel_calls=tf.data.experimental.AUTOTUNE)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
 

Tài nguyên bổ sung