Path: blob/master/site/pt-br/guide/data_performance_analysis.md
25115 views
Análise do desempenho de tf.data
com o TF Profiler
Visão geral
Este guia pressupõe conhecimentos do TensorFlow Profiler e tf.data
. O objetivo é fornecer instruções passo a passo com exemplos para ajudar a diagnosticar e corrigir problemas no desempenho de pipelines de entrada.
Para começar, colete um perfil do seu trabalho do TensorFlow. Confira as instruções para CPUs/GPUs e TPUs na nuvem.
O foco do workflow de análise abaixo é a ferramenta trace viewer (visualizador de rastreamento) do Profiler. Essa ferramenta exibe uma linha do tempo mostrando a duração das operações executadas por seu programa do TensorFlow e permite identificar quais operações levam mais tempo para executar. Confira mais informações sobre o trace viewer nesta seção do guia do TF Profiler. De forma geral, os eventos do tf.data
são exibidos na linha do tempo de CPU do host.
Workflow da análise
Pedimos que siga o workflow abaixo. Se você tiver algum comentário sobre como melhorá-lo, crie um Issue no GitHub com o rótulo “comp:data”.
1. O seu pipeline de tf.data
está gerando dados rápido o suficiente?
Comece identificando se o pipeline de entrada é o gargalo do seu programa do TensorFlow.
Basta procurar operações IteratorGetNext::DoCompute
no trace viewer. De forma geral, espera-se que você veja essas operações no começo de um passo. Esses fragmentos representam o tempo que seu pipeline de entrada demora para gerar um lote de elementos, quando solicitado. Se você estiver usando o Keras ou fazendo a iteração do dataset em uma função tf.function
, essas operações deverão estar nos threads tf_data_iterator_get_next
.
Se você estiver usando uma estratégia de distribuição, poderá ver eventos IteratorGetNextAsOptional::DoCompute
em vez de IteratorGetNext::DoCompute
(a partir do TF 2.3).
Se as chamadas retornarem rapidamente (<= 50 us), os seus dados ficam disponíveis quando solicitados. O pipeline de entrada não é o seu gargalo. Confira dicas de análise de desempenho mais genéricas no guia do Profiler.
Se as chamadas demorarem para retornar, o tf.data
não consegue acompanhar as solicitações do consumidor. Prossiga para a próxima seção.
2. Você está fazendo a pré-busca dos dados?
A prática recomendada para um bom desempenho do pipeline de entrada é inserir uma transformação tf.data.Dataset.prefetch
no final do pipeline de tf.data
. Essa transformação faz a sobreposição da computação do pré-processamento do pipeline de entrada com o próximo passo da computação do modelo e é necessária para um desempenho ideal do pipeline de entrada ao treinar o seu modelo. Se você estiver fazendo a pré-busca dos dados, deverá ver um segmento Iterator::Prefetch
no mesmo thread da operação IteratorGetNext::DoCompute
.
Se você não tiver uma pré-busca
no final do pipeline, precisa adicionar uma. Confira mais recomendações de desempenho do tf.data
no guia de desempenho do tf.data.
Se você já estiver fazendo a pré-busca dos dados e o pipeline de entrada ainda for o garglo, prossiga para a próxima seção para analisar o desempenho mais detalhadamente.
3. Está ocorrendo alta utilização da CPU?
tf.data
consegue uma alta taxa de transferência tentando fazer o melhor uso possível dos recursos disponíveis. De forma geral, até mesmo ao executar o seu modelo em um acelerador, como uma GPU ou TPU, os pipelines do tf.data
são executados na CPU. Para verificar a utilização, você pode usar ferramentas como sar e htop, ou pode conferir no console de monitoramento na nuvem se estiver executando na GCP.
Se a utilização for baixa, isso indica que seu pipeline de entrada pode não ser aproveitando ao máximo a CPU do host. Consulte as práticas recomendadas no guia de desempenho do tf.data. Se você tiver seguido as práticas recomendadas e a utilização e taxa de transferência permanecerem baixas, prossiga para a seção Análise do gargalo abaixo.
Se a utilização estiver se aproximando do limite de recursos, para melhorar o desempenho, você precisará aumentar a eficiência do pipeline de entrada (por exemplo, evitando computações desnecessárias) ou descarregar as computações.
Para aumentar a eficiência do pipeline de entrada, você pode evitar computações desnecessárias no tf.data
. Uma forma de fazer isso é inserir uma transformação tf.data.Dataset.cache
após trabalhos que fazem muita computação se os dados couberem na memória, pois isso reduz as computações, ao custo do aumento de uso da memória. Além disso, desativar o paralelismo intraoperações no tf.data
tem o potencial de aumentar a eficiência em mais de 10%. Para desativá-lo, defina a seguinte opção em seu pipeline de entrada:
4. Análise do gargalo
Esta seção explica como analisar os eventos do tf.data
no trace viewer para entender onde o gargalo está e as possíveis estratégias de mitigação.
Sobre os eventos do tf.data
no Profiler
Cada evento do tf.data
no Profiler tem o nome Iterator::<Dataset>
, em que <Dataset>
é o nome da fonte ou transformação do dataset. Cada evento também tem o nome longo Iterator::<Dataset_1>::...::<Dataset_n>
, que pode ser visto clicando no evento do tf.data
. No nome longo, <Dataset_n>
corresponde a <Dataset>
do nome curto, e os outros datasets no nome longo representam transformações dowstream.
Por exemplo, a captura de tela acima foi gerada pelo seguinte código:
Aqui, o evento Iterator::Map
tem o nome longo Iterator::BatchV2::FiniteRepeat::Map
. Observe que o nome dos datasets pode ser ligeiramente diferente do nome da API do Python (por exemplo, FiniteRepeat em vez de Repeat), mas deve ser intuitivo para sua análise.
Transformações síncronas e assíncronas
Para transformações síncronas do tf.data
(como Batch
e Map
), você verá eventos de transformações upstream no mesmo thread. No exemplo acima, como todas as transformações usadas são síncronas, todos os eventos aparecem no mesmo thread.
Para transformações assíncronas (como Prefetch
, ParallelMap
, ParallelInterleave
e MapAndBatch
), os eventos das transformações upstreams ficarão em um thread diferente. Nesses casos, o nome longo pode ajudar a identificar a qual transformação em um pipeline um determinado evento corresponde.
Por exemplo, a captura de tela acima foi gerada pelo seguinte código:
Aqui, os eventos Iterator::Prefetch
estão nos threads tf_data_iterator_get_next
. Como Prefetch
é assíncrono, seus eventos de entrada (BatchV2
) estarão em um thread diferente e podem ser localizados procurando-se o nome longo Iterator::Prefetch::BatchV2
. Neste caso, eles estão no thread tf_data_iterator_resource
. Pelo nome longo, você pode deduzir que BatchV2
é o upstream do evento Prefetch
. Além disso, o parent_id
do evento BatchV2
corresponderá ao ID do evento Prefetch
.
Identificação do gargalo
De forma geral, para identificar o gargalo em seu pipeline de entrada, percorra-o da transformação mais externa até a fonte. Começando pela transformação final em seu pipeline, percorra as transformações upstream até encontrar uma transformação lenta ou se deparar com um dataset fonte, como TFRecord
. No exemplo acima, você começaria por Prefetch
, depois seguiria upstream até BatchV2
, FiniteRepeat
, Map
e por fim Range
.
De forma geral, uma transformação lenta tem eventos longos, mas com eventos de entrada curtos. Confira alguns exemplos abaixo.
Observe que a transformação final (mais externa) na maioria dos pipelines de entrada de host é o evento Iterator::Model
. A transformação de modelos é introduzida automaticamente pelo runtime do tf.data
e é usada para instrumentação e tunagem automática do desempenho do pipeline de entrada.
Se o seu trabalho estiver usando uma estratégia de distribuição, o trace viewer conterá eventos adicionais que correspondem ao pipeline de entrada do dispositivo. A transformação mais externa do pipeline do dispositivo (aninhada em IteratorGetNextOp::DoCompute
ou IteratorGetNextAsOptionalOp::DoCompute
) será um evento Iterator::Prefetch
com um evento upstream Iterator::Generator
. Para encontrar o pipeline do host correspondente, procure eventos Iterator::Model
.
Exemplo 1
A captura de tela acima foi gerada a partir do seguinte pipeline de entrada:
Na captura de tela, observe que (1) os eventos Iterator::Map
são longos, mas (2) seus eventos de entrada (Iterator::FlatMap
) retornam rapidamente, indicando que a transformação Map sequencial é o gargalo.
Observe que, na captura de tela, o evento InstantiatedCapturedFunction::Run
corresponde ao tempo que demora para executar a função Map.
Exemplo 2
A captura de tela acima foi gerada a partir do seguinte pipeline de entrada:
Esse exemplo é similar ao acima, mas usa um ParallelMap em vez de Map. Note aqui que (1) os eventos Iterator::ParallelMap
são longos, mas (2) seus eventos de entrada Iterator::FlatMap
(que estão em um thread diferente, já que ParallelMap é assíncrono) são curtos. Isso indica que a transformação ParallelMap é o gargalo.
Como resolver o gargalo
Datasets fonte
Se você tiver identificado um dataset fonte como o gargalo, como ao ler arquivos TFRecord, pode melhorar o desempenho fazendo a paralelização da extração de dados. Para fazer isso, garanta que os dados sejam fragmentos em diversos arquivos e use tf.data.Dataset.interleave
com o parâmetro num_parallel_calls
definido como tf.data.AUTOTUNE
. Se determinismo não for importante para o seu programa, é possível melhorar ainda mais o desempenho definindo o sinalizador deterministic=False
de tf.data.Dataset.interleave
a partir do TF 2.2. Por exemplo, se você estiver lendo TFRecords, pode fazer o seguinte:
Observe que os arquivos fragmentados devem ser grandes o suficiente para compensar a sobrecarga de abrir um arquivo. Confira mais detalhes sobre extração paralela de dados nesta seção do guia de desempenho do tf.data
.
Datasets de transformação
Se você tiver identificado uma transformação intermediária do tf.data
como gargalo, pode resolver esse problema paralelizando a transformação ou fazendo o cache das computações se os dados couberem na memória e se for apropriado. Algumas transformações, como Map
, têm contrapartidas de paralelização: o guia de desempenho do tf.data
demonstra como fazer a paralelização. Outras transformações, como Filter
, Unbatch
e Batch
, são inerentemente sequenciais. Para paralelizá-las, você pode introduzir “paralelismo externo”. Por exemplo, supondo que seu pipeline de entrada pareça inicialmente como o abaixo, com Batch
sendo o gargalo:
Você pode incorporar o “paralelismo externo” executando diversas cópias do pipeline de entrada em entradas fragmentadas e combinando os resultados:
Recursos adicionais
Guia de desempenho do tf.data sobre como criar pipelines de entrada do
tf.data
que tenham bom desempenhoVídeo "Por Dentro do TensorFlow": práticas recomendadas para
tf.data