Infraestrutura

A infraestrutura de testes do PyTorch é frequentemente gerada dinamicamente em diferentes dispositivos e tipos de dados, o que explica por que os nomes dos testes no CI podem parecer diferentes dos nomes das classes e métodos no arquivo-fonte. Este artigo explica como os testes genéricos de dispositivos, OpInfos, instantiate_device_type_tests() e fragmentação de CI se encaixam, e como os contribuintes podem executar e depurar os testes do PyTorch de forma mais eficaz. Se você já abriu uma solicitação de pull request para o PyTorch, assistiu a um teste gerado, como TestLinalgCUDA.test_matmul_cuda_float32, falhar no CI e se perguntou de onde veio esse nome – ou tentou executar um teste pelo nome da fonte e obteve “nenhum teste coletado” – este guia é para você.

A infraestrutura de testes do PyTorch é projetada para escala. Dependendo dos decoradores utilizados e dos metadados do operador fornecidos por meio de OpInfos, um método de teste único pode expandir-se em vários dispositivos, tipos de dados e operadores automaticamente. É isso que permite que o PyTorch valide milhares de combinações sem que sejam necessários milhares de testes manuais. No entanto, isso também significa que o teste que você escreve no arquivo-fonte nem sempre é o teste exato que o CI executa, o que pode ser confuso na primeira vez que você o encontra. É importante notar que muitos ajudantes discutidos neste guia estão localizados em torch.testing._internal, que é a infraestrutura de teste interna do PyTorch. Se você estiver testando seu próprio projeto, use as APIs públicas, como pytest e torch.testing.assert_close, em vez disso.

Um dos primeiros momentos confusos para novos contribuintes do PyTorch é tentar executar um teste pelo nome da classe e do método que eles veem no arquivo-fonte. Em muitos arquivos de teste do PyTorch, isso pode retornar “nenhum teste coletado”. Isso geralmente não ocorre porque o teste está faltando, mas porque a classe no arquivo-fonte é um modelo, e não a classe final que o executor de teste vê. Quando o arquivo é importado, instantiate_device_type_tests() expande o modelo em classes concretas específicas do dispositivo, como TestTorchCPU, TestTorchCUDA ou TestTorchMPS. Se o teste também for parametrizado por tipo de dados, o nome do método gerado pode incluir o dispositivo e o tipo de dados, por exemplo, test_matmul_cuda_float32. Essas classes geradas são construídas a partir da classe de modelo original e das bases de teste específicas do dispositivo do PyTorch, portanto, elas ainda herdam o comportamento compartilhado fornecido pela classe de teste interna do PyTorch.

Para depuração local, geralmente é mais fácil filtrar pelo padrão de nome do teste gerado em vez de mirar na classe de modelo original diretamente. Uma vez que você saiba que o PyTorch gera os nomes dos testes executáveis durante a importação, as falhas do CI se tornam muito mais fáceis de mapear para o teste de origem. O PyTorch é executado em dispositivos como CPU, CUDA, MPS e XPU, e muitos testes precisam validar o comportamento em vários tipos de dados, como float16, float32, float64, bfloat16, integer e outros. Escrever um teste separado para cada combinação de dispositivo e tipo de dados rapidamente se tornaria um pesadelo de manutenção. Portanto, o PyTorch usa modelos de teste. Você escreve um método de teste com parâmetros de dispositivo e tipo de dados.

Quando o Python importa o arquivo de teste, instantiate_device_type_tests() expande esse modelo em dispositivos e tipos de dados selecionados. Por exemplo, uma classe de modelo pode produzir classes geradas como TestMatmulCPU, TestMatmulCUDA e TestMatmulMPS, com métodos gerados como test_basic_cuda_float32. Os nomes gerados seguem um padrão específico, portanto, um modelo como TestMatmul.test_basic pode se tornar TestMatmulCUDA.test_basic_cuda_float32. O dispositivo aparece em letras maiúsculas no nome da classe e em letras minúsculas no nome do método. É por isso que as falhas do CI mostram nomes gerados em vez de apenas o nome do modelo que você escreveu. O nome gerado informa exatamente qual dispositivo e tipo de dados falharam.

A infraestrutura de teste do PyTorch é mais fácil de entender como um conjunto de camadas conectadas. Os contribuintes geralmente interagem com as camadas médias: instanciamento de dispositivos, decoradores de parametrização, OpInfos e utilitários de teste. A orquestração do CI fica acima delas, enquanto as utilidades básicas fornecem a base compartilhada. OpInfos são entradas de metadados que descrevem como um operador do PyTorch deve ser testado. Em vez de escrever um teste separado para cada operador, o PyTorch usa modelos de teste genéricos que lêem os metadados do OpInfo e executam as mesmas verificações em vários operadores.

Um OpInfo pode definir coisas como o nome do operador, variantes, tipos de dados suportados, entradas de amostra, skips esperados, decoradores e regras de tolerância. Testes genéricos em arquivos como test_ops.py consomem op_db por meio de @ops(…), que passa o operador selecionado, o dispositivo e o tipo de dados para o teste. Isso é como uma única entrada de operador pode participar de muitos tipos de cobertura: correção de encaminhamento, comportamento de tipo de dados e dispositivo, verificações de gradiente, caminhos relacionados à compilação e validação de estilo Meta/FakeTensor – dependendo do teste e dos metadados do operador. Portanto, quando você vê um teste gerado, como TestCommonCUDA.test_variant_consistency_eager_torch_matmul_cuda_float32, geralmente significa que um teste genérico baseado em OpInfo está sendo executado contra o OpInfo do torch.matmul para um dispositivo e tipo de dados específicos.

O decorador @ops(…) é um exemplo do