이 페이지는 Cloud Translation API를 통해 번역되었습니다.
Switch to English

TF 프로파일 러로 tf.data 성능 분석

개요

이 안내서는 TensorFlow Profilertf.data 익숙하다고 가정합니다. 사용자가 입력 파이프 라인 성능 문제를 진단하고 수정하는 데 도움이되는 예제와 함께 단계별 지침을 제공하는 것을 목표로합니다.

시작하려면 TensorFlow 작업의 프로파일을 수집하십시오. 그렇게하는 방법에 대한 지침은 CPU / GPUCloud TPU에 사용할 수 있습니다.

TensorFlow Trace Viewer

아래에 자세히 설명 된 분석 워크 플로우는 프로파일 러의 추적 뷰어 도구에 중점을 둡니다. 이 도구는 TensorFlow 프로그램이 실행하는 작업 기간을 표시하는 타임 라인을 표시하며 가장 오래 실행하는 작업을 식별 할 수 있습니다. 추적 뷰어에 대한 자세한 정보는 TF 프로파일 러 안내서 의이 섹션 을 확인하십시오. 일반적으로 tf.data 이벤트는 호스트 CPU 타임 라인에 나타납니다.

분석 워크 플로우

아래의 워크 플로우를 따르십시오. 개선에 도움이되는 의견이 있으면“comp : data”레이블 로 github 문제만드십시오 .

1. tf.data 파이프 라인이 데이터를 충분히 빠르게 생성합니까?

입력 파이프 라인이 TensorFlow 프로그램의 병목 상태인지 확인하여 시작하십시오.

그렇게하려면 추적 뷰어에서 IteratorGetNext::DoCompute ops를 IteratorGetNext::DoCompute . 일반적으로, 당신은 단계의 시작 부분에서 이것을 볼 것으로 기대합니다. 이 슬라이스는 입력 파이프 라인이 요청 될 때 배치 요소를 생성하는 데 걸리는 시간을 나타냅니다. keras를 사용하거나 tf.function 에서 데이터 세트를 반복하는 경우 tf_data_iterator_get_next 스레드에서 찾을 수 있습니다.

당신이 사용하는 경우주의 유통 전략을 , 당신이 볼 수 IteratorGetNextAsOptional::DoCompute 대신 이벤트 IteratorGetNext::DoCompute (TF 2.3 기준).

image

전화가 빨리 돌아 가면 (<= 50 us), 요청시 데이터를 사용할 수 있음을 의미합니다. 입력 파이프 라인은 병목 현상이 아닙니다. 보다 일반적인 성능 분석 팁은 프로파일 러 안내서 를 참조하십시오.

image

호출이 느리게 반환되면 tf.data 가 소비자의 요청을 따라 잡을 수 없습니다. 다음 섹션으로 계속 진행하십시오.

2. 데이터를 프리 페치합니까?

입력 파이프 라인 성능에 대한 모범 사례는 tf.data 파이프 라인 끝에 tf.data.Dataset.prefetch 변환을 삽입하는 tf.data.Dataset.prefetch 입니다. 이 변환은 입력 파이프 라인의 전처리 계산을 다음 모델 계산 단계와 겹치며 모델 학습시 최적의 입력 파이프 라인 성능을 위해 필요합니다. 데이터를 프리 페치하는 경우 IteratorGetNext::DoCompute op와 동일한 스레드에 Iterator::Prefetch 슬라이스가 표시됩니다.

image

파이프 라인 끝에 prefetch 가없는 경우 prefetch 추가해야합니다. tf.data 성능 권장 사항에 대한 자세한 정보는 tf.data 성능 안내서를 참조하십시오.

이미 데이터를 프리 페치 하고 있고 입력 파이프 라인이 여전히 병목 현상 인 경우 다음 섹션으로 계속 진행하여 성능을 자세히 분석하십시오.

3. 높은 CPU 사용률에 도달하고 있습니까?

tf.data 는 사용 가능한 리소스를 최대한 활용하여 높은 처리량을 달성합니다. 일반적으로 GPU 또는 TPU와 같은 가속기에서 모델을 실행할 tf.data 파이프 라인은 CPU에서 실행됩니다. sarhtop 과 같은 도구를 사용하거나 GCP에서 실행중인 경우 클라우드 모니터링 콘솔 에서 사용률을 확인할 수 있습니다.

사용률이 낮 으면 입력 파이프 라인이 호스트 CPU를 충분히 활용하지 못할 수 있습니다. 모범 사례는 tf.data 성능 안내서 를 참조하십시오. 모범 사례를 적용하고 활용률과 처리량이 낮은 경우 아래 병목 현상 분석을 계속하십시오.

사용률이 리소스 제한에 근접한 경우 성능을 더욱 향상 시키려면 입력 파이프 라인의 효율성을 개선하거나 (예 : 불필요한 계산 방지) 오프로드 계산을 수행해야합니다.

tf.data 에서 불필요한 계산을 피함으로써 입력 파이프 라인의 효율성을 향상시킬 수 있습니다. 이를 수행하는 한 가지 방법은 데이터가 메모리에 맞는 경우 계산 집약적 인 작업 후에 tf.data.Dataset.cache 변환을 삽입하는 tf.data.Dataset.cache . 이렇게하면 메모리 사용량이 증가하면서 계산이 줄어 듭니다. 또한 tf.data 에서 intra-op 병렬 처리를 비활성화하면 효율성이> 10 % 증가 할 수 있으며 입력 파이프 라인에서 다음 옵션을 설정하여 수행 할 수 있습니다.

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

4. 병목 현상 분석

다음 섹션에서는 트레이스 뷰어에서 tf.data 이벤트를 읽고 병목 현상의 위치와 가능한 완화 전략을 이해하는 방법을 tf.data 합니다.

프로파일 러의 tf.data 이벤트 이해

프로파일 러의 각 tf.data 이벤트 이름은 Iterator::<Dataset> . 여기서 <Dataset> 은 데이터 세트 소스 또는 변환의 이름입니다. 각 이벤트에는 긴 이름 Iterator::<Dataset_1>::...::<Dataset_n> 있으며 tf.data 이벤트를 클릭하면 볼 수 있습니다. 긴 이름에서 <Dataset_n> 은 (짧은) 이름의 <Dataset> <Dataset_n> 과 일치하고 긴 이름의 다른 데이터 집합은 다운 스트림 변환을 나타냅니다.

image

예를 들어 위의 스크린 샷은 다음 코드에서 생성되었습니다.

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

여기서 Iterator::Map 이벤트의 이름은 Iterator::BatchV2::FiniteRepeat::Map 입니다. 데이터 세트 이름은 Python API와 약간 다를 수 있지만 (예 : 반복 대신 FiniteRepeat) 구문 분석 할 수있을 정도로 직관적이어야합니다.

동기식 및 비동기식 변환

동기식 tf.data 변환 (예 : BatchMap )의 경우 동일한 스레드에서 업스트림 변환의 이벤트가 표시됩니다. 위 예제에서 사용 된 모든 변환은 동기식이므로 모든 이벤트가 동일한 스레드에 나타납니다.

업스트림 변환의 비동기 변환 (예 : Prefetch , ParallelMap , ParallelInterleaveMapAndBatch ) 이벤트의 경우 다른 스레드에 있습니다. 이러한 경우 "긴 이름"은 이벤트가 해당하는 파이프 라인의 변환을 식별하는 데 도움이됩니다.

image

예를 들어 위의 스크린 샷은 다음 코드에서 생성되었습니다.

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

Iterator::Prefetch 이벤트는 tf_data_iterator_get_next 스레드에 있습니다. Prefetch 는 비동기식이므로 입력 이벤트 ( BatchV2 )는 다른 스레드에 있으며 긴 이름 Iterator::Prefetch::BatchV2 를 검색하여 찾을 수 있습니다. 이 경우 tf_data_iterator_resource 스레드에 있습니다. 긴 이름에서 BatchV2Prefetch 업스트림이라고 추론 할 수 있습니다. 또한 BatchV2 이벤트의 parent_idPrefetch 이벤트의 ID와 일치합니다.

병목 현상 식별

일반적으로 입력 파이프 라인의 병목 현상을 식별하려면 입력 파이프 라인을 가장 바깥 쪽 변환에서 소스까지 연결하십시오. 파이프 라인의 최종 변환에서 시작하여 느린 변환을 찾거나 TFRecord 와 같은 소스 데이터 세트에 도달 할 때까지 업스트림 변환으로 TFRecord . 위의 예에서 Prefetch 에서 시작하여 BatchV2 , FiniteRepeat , Map 및 마지막으로 Range 로 업스트림합니다.

일반적으로 느린 변환은 이벤트는 길지만 입력 이벤트는 짧은 변환에 해당합니다. 몇 가지 예는 다음과 같습니다.

대부분의 호스트 입력 파이프 라인에서 마지막 (가장 바깥 쪽) 변환은 Iterator::Model 이벤트입니다. 모델 변환은 tf.data 런타임에 의해 자동으로 도입되며 입력 파이프 라인 성능을 계측하고 자동 튜닝하는 데 사용됩니다.

작업에서 분배 전략을 사용하는 경우 추적 뷰어에는 장치 입력 파이프 라인에 해당하는 추가 이벤트가 포함됩니다. 장치 파이프 라인의 가장 바깥 쪽 변환 ( IteratorGetNextOp::DoCompute 또는 IteratorGetNextAsOptionalOp::DoCompute 아래에 중첩 IteratorGetNextAsOptionalOp::DoCompute )은 업스트림 Iterator::Generator 이벤트가있는 Iterator::Prefetch 이벤트입니다. Iterator::Model 이벤트를 검색하여 해당 호스트 파이프 라인을 찾을 수 있습니다.

실시 예 1

image

위 스크린 샷은 다음 입력 파이프 라인에서 생성됩니다.

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

스크린 샷에서 (1) Iterator::Map 이벤트는 길지만 (2) 입력 이벤트 ( Iterator::FlatMap )가 빠르게 반환되는 것을 Iterator::FlatMap . 이는 순차적 맵 변환이 병목 현상임을 나타냅니다.

스크린 샷에서 InstantiatedCapturedFunction::Run 이벤트는 맵 기능을 실행하는 데 걸리는 시간에 해당합니다.

실시 예 2

image

위 스크린 샷은 다음 입력 파이프 라인에서 생성됩니다.

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

이 예제는 위와 비슷하지만 Map 대신 ParallelMap을 사용합니다. 여기서는 (1) Iterator::ParallelMap 이벤트가 길지만 (2) 입력 이벤트 Iterator::FlatMap (ParallelMap이 비동기이기 때문에 다른 스레드에 있음)이 짧다는 것을 알 수 있습니다. 이는 ParallelMap 변환이 병목 현상임을 나타냅니다.

병목 현상 해결

소스 데이터 셋

데이터 세트 소스를 TFRecord 파일에서 읽는 것과 같은 병목 현상으로 식별 한 경우 데이터 추출을 병렬화하여 성능을 향상시킬 수 있습니다. 그렇게하려면 데이터가 여러 파일에 걸쳐 tf.data.Dataset.interleave 되고 num_parallel_calls 매개 변수가 tf.data.experimental.AUTOTUNE 설정된 tf.data.experimental.AUTOTUNE . 프로그램에 결정 성이 중요하지 않은 경우, TF 2.2에서 tf.data.Dataset.interleavedeterministic=False 플래그를 설정하여 성능을 추가로 향상시킬 수 있습니다. 예를 들어, TFRecords에서 읽는 경우 다음을 수행 할 수 있습니다.

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

샤딩 된 파일은 파일을 여는 오버 헤드를 상쇄하기 위해 합리적으로 커야합니다. 병렬 데이터 추출에 대한 자세한 내용은 tf.data 성능 안내서 의이 섹션 을 참조하십시오.

변환 데이터 세트

중간 tf.data 변환을 병목 현상으로 식별 한 경우, 데이터가 메모리에 적합하고 적절한 경우 변환을 병렬화하거나 계산을 캐싱하여 이를 해결할 수 있습니다. Map 과 같은 일부 변환에는 평행 한 부분이 있습니다. tf.data 성능 안내서는이 를 병렬화하는 방법을 보여 줍니다. Filter , UnbatchBatch 와 같은 다른 변환은 본질적으로 순차적입니다. "외부 병렬 처리"를 도입하여 병렬화 할 수 있습니다. 예를 들어, 입력 파이프 라인이 초기에 다음과 같으며 Batch 가 병목 현상으로 가정합니다.

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

샤드 된 입력에 대해 여러 개의 입력 파이프 라인 사본을 실행하고 결과를 결합하여 "외부 병렬 처리"를 도입 할 수 있습니다.

 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)
 

추가 자료