Path: blob/master/site/pt-br/federated/design/tracing.md
25118 views
Tracing
[TOC]
O tracing é o processo de construir uma AST a partir de uma função do Python.
TODO: b/153500547 – Descrever os componentes individuais do sistema de tracing e adicionar os respectivos links.
Tracing de uma computação federada
De forma geral, há três componentes ao fazer o tracing de uma computação federada.
Encapsulamento dos argumentos
Internamente, uma computação do TFF tem apenas nenhum ou um argumento. Os argumentos fornecidos pelo decorador federated_computation.federated_computation descrevem a assinatura do tipo dos argumentos para a computação do TFF, que usa essas informações para determinar como encapsular os argumentos da função do Python em uma única structure.Struct (estrutura).
Observação: o uso de uma Struct
como uma única estrutura de dados para representar tanto os args
quanto os kwargs
do Python é o motivo de a Struct
aceitar campos com nome e sem nome.
Confira mais informações em function_utils.create_argument_unpacking_fn.
Tracing da função
Ao fazer o tracing de uma federated_computation
(computação federada), a função do usuário é chamada usando-se value_impl.Value como uma substituição de cada argumento. Value
tenta emular o comportamento do tipo de argumento original implementando os métodos dunder comuns do Python (por exemplo, __getattr__
).
Quando há exatamente um argumento, o tracing é obtido:
Construindo um value_impl.Value associado a uma building_blocks.Reference com a assinatura de tipo apropriada para representar o argumento.
Invocando a função no
Value
, o que faz o runtime do Python invocar os métodos dunder implementados peloValue
, que traduz esses métodos dunder em uma construção da AST. Cada método dunder constrói uma AST e retorna umValue
associado a essa AST.
Por exemplo:
Aqui, o parâmetro da função é uma tupla e, no corpo da função, o elemento no índice 0 é selecionado. Isso invoca o método __getitem__
do Python, que é sobrescrito no Value
. No caso mais simples, a implementação de Value.__getitem__
constrói uma building_blocks.Selection para representar a invocação de __getitem__
e retorna um Value
associado a essa nova Selection
.
O tracing continua porque cada método dunder retorna um Value
, eliminando toda operação no corpo da função, o que faz um dos métodos dunder sobrescritos ser invocado.
Construção da AST
O resultado do tracing da função é encapsulado em um building_blocks.Lambda, cujos parameter_name
e parameter_type
mapeiam para a building_block.Reference criada para representar os argumentos encapsulados. O Lambda
resultante é retornado como um objeto Python que representa totalmente a função do Python do usuário.
Tracing de uma computação do TensorFlow
TODO: b/153500547 – Descrever o processo de fazer o tracing de uma computação do TensorFlow.
Limpar as mensagens de erro das exceções durante o tracing
Em um dado momento na história do TFF, para fazer tracing da computação do usuário, era preciso passar por diversas funções encapsuladoras antes de chamar a função do usuário, o que trazia o efeito indesejado de gerar mensagens de erro como esta:
Era muito difícil encontrar a linha de código que continha o bug nesse traceback, o que fazia os usuários comunicarem esses problemas como bug do TFF e geralmente dificultava sua vida.
Atualmente, o TFF garante que essas pilhas de chamadas estejam livres de funções extras do TFF. Esse é o motivo para usar geradores no código de tracing do TFF, geralmente em padrões como este:
Com esse padrão, o código do usuário (user_fn
acima) pode ser chamado no nível superior da pilha de chamadas, ao mesmo tempo em que permite que seus argumentos, saída e até mesmo contexto local dos threads sejam manipulados pelas funções encapsuladoras.
Algumas versões simples desse padrão podem ser substituídas por funções "before" (antes) e "after" (depois). Por exemplo, foo
acima pode ser substituído por:
Deve-se optar por esse padrão em casos que não exijam estado compartilhado entre as partes "before" e "after". Porém, pode ser complicado expressar dessa forma casos mais complexos que envolvam estados complexos ou gerenciadores de contexto:
Neste último exemplo, fica bem menos claro qual código está sendo executado dentro de um contexto, e fica ainda menos claro quando mais estados são compartilhados entre as partes before e after.
Diversas outras soluções para o problema geral de "ocultar funções do TFF nas mensagens de erro de usuários" foram tentadas, incluindo capturar e gerar novamente exceções (isso falhou devido à incapacidade de criar uma exceção cuja pilha incluía somente o nível mais baixo do código de usuário, sem incluir o código que o chamou); capturar exceções e substituir o traceback por outro filtrado (o que é específico ao CPython e não é compatível com a linguagem Python); e substituir o manipulador de exceções (o que falhou porque sys.excepthook
não é usado por absltest
e é sobrescrito por outros frameworks). No fim, a inversão de controle baseada no gerador foi a que propiciou a melhor experiência para o usuário final ao custo de uma certa complexidade de implementação no TFF.