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 visam garantir 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 atuais 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 funções de forma existentes de cada operação precisam ser revisados para estar 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 op não está disponível, a implementação de XLA precisa ser usada como fonte da verdade, incluindo xla/service/shape_inference.cc e xla/service/hlo_verifier.cc. A implementação de XLA não cobre dinamismo ilimitado. Portanto, para dinamismo ilimitado, aplicaremos o bom senso até que o RFC de dinamismo esteja disponível.

(P2) Aproveitar ao máximo a 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. Portanto, 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 o ODS, porque ele nunca será acionado.

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 a seguinte: permitir que os verificadores verifiquem os operandos/atributos e, em seguida, permitir que as funções de forma calculem apenas tipos de resultados inferidos e verifiquem 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 da forma.
  • Código duplicado: por exemplo, em verificadores, fazemos algum processamento nos operandos e, em seguida, verificamos alguns resultados intermediários. Em seguida, as 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 têm dois métodos diferentes.

A solução é esta:

  1. Para a maioria das operações sem regiões (como PadOp): tente colocar todo o código de verificação nas funções de forma e descarte os verificadores totalmente. Nos casos em que isso não é possível devido à incapacidade de inferir tipos de retorno (como com ReshapeOp ou BroadcastInDimOp), crie um verificador para conter a lógica de verificação necessária. As operações normalmente inferíveis, como AddOp, ainda podem precisar de um verificador para realizar outras verificações, porque verificam restrições de um tipo de retorno fornecido, que não pode ser acessado nos métodos de inferência de tipo/forma.

  2. Para operações com regiões (como ReduceOp/IfOp, confira uma lista completa aqui): os builders 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 inferência de tipo (como ReduceOp), coloque a lógica de verificação relacionada à região em verificadores em vez de funções de forma. Duplique alguns códigos se for inevitável.

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

(P4) Estabelecer diretrizes de teste

Precisamos adicionar/manter testes cobertos pelo ODS?

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

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

Onde colocamos testes para 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 verifica se o tipo de retorno inferido é compatível com o tipo de resultado real (diferente de!).

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 verifica se o tipo inferido corresponde exatamente ao esperado. Um teste positivo por operação em geral.

O que você precisa fazer

Ao implementar ou revisitar o verificador e/ou a função de forma de uma operação:

  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 operação for complicada e puder conter muitos testes, adicione um arquivo de teste separado chamado verify_<op_name>.mlir ou verify_<your_topic>.mlir na mesma pasta.