Inferência de tipo

O StableHLO foi originalmente inicializado do dialeto MHLO e herdou a implementação da MHLO de inferência de tipo. O progresso da implementação é monitorado em status.md.

As diretrizes propostas abaixo garantem a implementação de verificadores e funções de forma de alta qualidade para operações StableHLO.

Proposta

Essas propostas se aplicam à revisão de implementações existentes e à realização de novas operações até uma cobertura abrangente.

(P1) Usar a especificação StableHLO como fonte da verdade

A spec é a fonte da verdade para todos os verificadores e funções de forma das operações do StableHLO. Os verificadores e as funções de forma existentes de cada operação precisam ser revisados para que estejam totalmente alinhados com a especificação. Observe que o documento de especificação continua em evolução. Nos casos em que a especificação de uma operação não estiver disponível, a implementação do XLA precisa ser usada como a fonte da verdade, incluindo xla/service/shape_inference.cc e xla/service/hlo_verifier.cc. A implementação de XLA não abrange dinamismo ilimitado. Portanto, para dinamismo ilimitado, aplicaremos o bom senso até que o RFC de dinamismo esteja disponível.

(P2) Aproveite ao máximo o ODS

Os arquivos ODS (como StablehloOps.td) definem operações com características e tipos para cada operando/atributo/resultado e fazem as verificações. Assim, NENHUM código de verificação é necessário nos verificadores ou funções de forma para as propriedades que já são garantidas pelo ODS. Remova o código de verificação se estiver duplicado com ODS, porque eles nunca serão acionados.

Precisamos adicionar testes para as restrições do ODS? Consulte Estabelecer diretrizes de teste.

(P3) Manter o código de verificação em verificadores e funções de forma

Ambos:

  • verifiers: implementados por Op::verify() e
  • Funções de forma: implementadas por InferTypeOpInterfaces, como Op::inferReturnTypes() ou Op::inferReturnTypeComponents.

pode ter um código de verificação para conferir operandos/atributos/resultados; Uma divisão inicial pode ser da seguinte maneira: permita que os verificadores verifiquem os operandos/atributos e, em seguida, deixe as funções de forma calcularem apenas os tipos de resultados inferidos e verificarem a compatibilidade com os tipos de resultados reais. No entanto, na realidade, essa divisão tem alguns problemas:

  • A função de forma pode ser chamada pelas funções build() geradas automaticamente, sem chamar o verificador primeiro. Portanto, as entradas relacionadas também precisam ser verificadas na função de forma.
  • Código duplicado: por exemplo, em verificadores, fazemos algum processamento nos operandos e, em seguida, verificamos alguns resultados intermediários. Em seguida, nas funções de forma, esses resultados intermediários são úteis para inferir os resultados finais. Esses resultados intermediários precisam ser calculados duas vezes.
  • Carga de manutenção: as verificações de uma operação são contidas em dois métodos diferentes.

A solução é a seguinte:

  1. Para a maioria das operações sem regiões (como PadOp): coloque todo o código de verificação nas funções de forma e descarte totalmente os verificadores.

  2. Para operações com regiões (como ReduceOp/IfOp, confira uma lista completa neste link): os criadores gerados automaticamente não usam regiões como parâmetros. Portanto, se esses builders envolverem a inferência de tipo, a função de forma será chamada com regiões vazias (consulte este exemplo).

    1. Se as regiões não forem necessárias para a inferência de tipo (como ReduceOp), coloque a lógica de verificação relacionada à região nos verificadores em vez das funções de forma. Duplique alguns códigos se isso for inevitável.

    2. Se as regiões forem necessárias para a inferência de tipo (IfOp/CaseOp/MapOp), a função de forma precisará verificar explicitamente se as regiões não estão vazias explicitamente, mesmo que o ODS já garanta a existência dele na definição da operação.

(P4) Estabelecer diretrizes de teste

Precisamos adicionar/manter testes para verificações cobertas pelo ODS?

Nós não. Os testes precisam se concentrar nos verificadores e nas funções de forma, enquanto as mudanças no ODS precisam ser revisadas.

Tenha cuidado com as partes ausentes: por exemplo, se a operação tiver a característica SameOperandsAndResultShape, que verifica apenas formas, mas não o tipo de elemento, a verificação dos tipos de operandos/resultados de elemento ainda vai precisar de testes.

Onde colocamos testes de verificadores e inferência de tipo?

ops_stablehlo.mlir contém os casos positivos de ops e (pelo menos) um teste negativo para cada erro de verificação. Ela também consegue verificar se o tipo de retorno inferido é compatível com o tipo de resultado real (diferente de!).

O infer_stablehlo.mlir verifica a existência da função de forma de uma operação por linha com hlo_test_infer.get_return_type_components"(%x):... e confere se o tipo de inferência corresponde exatamente ao esperado. Um teste positivo por operação em geral.

O que fazer

Ao implementar ou revisar o verificador e/ou a função de forma de uma op:

  1. Coloque todos os casos positivos e negativos em ops_stablehlo.mlir.

  2. Adicione um único teste positivo em infer_stablehlo.mlir para testar a interface.

  3. (Opcional) Se uma op for complicada e puder conter muitos testes, considere adicionar um arquivo de teste separado chamado verify_<op_name>.mlir ou verify_<your_topic>.mlir na mesma pasta.