Tipo di inferenza

StableHLO è stato originariamente generato con bootstrapping dal dialetto MHLO e ha ereditato l'implementazione MHLO dell'inferenza dei tipi. L'avanzamento dell'implementazione è monitorato in status.md.

Le linee guida proposte di seguito hanno lo scopo di garantire l'implementazione di verificatori di alta qualità e funzioni di forma per le operazioni StableHLO.

Proposta

Queste proposte riguardano sia la rivisitazione delle implementazioni esistenti, sia il raggiungimento di nuove operazioni fino a una copertura completa.

(P1) Utilizzo delle specifiche StableHLO come fonte attendibile

La spec è la fonte attendibile per tutti i verificatori e le funzioni di forma delle operazioni StableHLO. I verificatori esistenti e le funzioni di forma di ogni operazione devono essere rivisitati per essere pienamente allineati alle specifiche. Notate che il documento delle specifiche è in continua evoluzione. Nei casi in cui le specifiche di un'operazione non siano disponibili, è preferibile utilizzare l'implementazione XLA come fonte attendibile, inclusi xla/service/shape_inference.cc e xla/service/hlo_verifier.cc. L'implementazione XLA non copre un dinamismo illimitato, quindi per un dinamismo illimitato applicheremo il buon senso fino a quando non sarà disponibile il dinamismo RFC.

(P2) Sfruttare al meglio le ODS

I file ODS (come StablehloOps.td) definiscono le operazioni con trait e tipi per ogni operando/attributo/risultato ed effettueranno le verifiche. Pertanto, NESSUN codice di verifica è necessario nelle verifiche o nelle funzioni di forma per le proprietà già garantite dalla ODS. Rimuovi il codice di verifica se duplicato con ODS, dato che non verranno mai attivati.

Dobbiamo aggiungere dei test per i vincoli dell'ODS? Consulta la pagina Stabilire linee guida per i test.

(P3) Mantenere il codice di verifica nelle verifiche e nelle funzioni di forma

Entrambi:

  • verifiers: implementate da Op::verify() e
  • funzioni di forma: implementate da InferTypeOpInterface come Op::inferReturnTypes() o Op::inferReturnTypeComponents

potrebbero includere un codice di verifica per controllare operandi/attributi/risultati. Una suddivisione iniziale potrebbe essere la seguente: consenti ai verificatori di controllare gli operandi/gli attributi, quindi lascia che le funzioni di forma calcolino solo i tipi di risultati dedotti e verifichi la compatibilità con i tipi di risultati reali. Tuttavia, questa divisione presenta alcuni problemi:

  • La funzione di forma può essere chiamata dalle funzioni build() generate automaticamente, senza prima chiamare lo strumento di verifica. Quindi anche i relativi input devono essere verificati nella funzione di forma.
  • Codice duplicato: ad esempio nelle verifiche, eseguiamo alcune elaborazioni sugli operandi e poi verifichiamo alcuni risultati intermedi. Nelle funzioni di forma, questi risultati intermedi sono utili per dedurre i risultati finali. Questi risultati intermedi devono essere calcolati due volte.
  • Oneri di manutenzione: le verifiche di un'operazione prevedono due metodi diversi.

La soluzione è la seguente:

  1. Per la maggior parte delle operazioni senza regioni (ad esempio PadOp): inserisci tutto il codice di verifica nelle funzioni di forma ed elimina completamente gli strumenti di verifica.

  2. Per le operazioni con le regioni (ad esempio ReduceOp/IfOp; un elenco completo è disponibile qui): i builder generati automaticamente non prendono le regioni come parametri. Pertanto, se questi builder prevedono l'inferenza del tipo, la funzione di forma verrà chiamata con regioni vuote (vedi questo esempio).

    1. Se le regioni non sono necessarie per l'inferenza del tipo (ad esempio ReduceOp), inserisci la logica di verifica relativa alla regione negli strumenti di verifica anziché nelle funzioni di forma. Duplicare del codice se è inevitabile.

    2. Se sono necessarie le regioni per l'inferenza del tipo (IfOp/CaseOp/MapOp), anche la funzione di forma deve verificare in modo esplicito che le regioni non siano vuote, anche se l'ODS potrebbe già garantire la sua esistenza nella definizione dell'operatore.

(P4) Stabilire linee guida per i test

Devono aggiungere/eseguire test per le verifiche coperte da ODS?

ma no. I test dovrebbero concentrarsi sulle verifiche e sulle funzioni di forma, mentre le modifiche alla funzione ODS richiedono una revisione di questa operazione.

Ma fai attenzione ai pezzi mancanti: ad esempio, se l'operazione contiene il trait SameOperandsAndResultShape, che controlla solo le forme ma non il tipo di elemento, la verifica dei tipi di operandi/risultati deve comunque essere testata.

Dove vengono posizionati i test per i verificatori e l'inferenza dei tipi?

ops_stablehlo.mlir contiene i casi positivi di operazioni e (almeno) un test negativo per ogni errore di verifica. È anche in grado di verificare che il tipo restituito dedotto sia compatibile con il tipo di risultato reale (non è lo stesso!).

infer_stablehlo.mlir verifica l'esistenza della funzione shape di un'operazione per riga con hlo_test_infer.get_return_type_components"(%x):... e verifica che il tipo dedotto corrisponda esattamente a come previsto. In generale, un test positivo per operazione.

Cosa fare

Quando implementi o riesegui la verifica della funzione di verifica e/o della forma di un'operazione:

  1. Inserisci tutti i casi positivi e negativi in ops_stablehlo.mlir.

  2. Aggiungi un singolo test positivo in infer_stablehlo.mlir per testare l'interfaccia.

  3. (Facoltativo) Se un'operazione è complicata e potrebbe contenere molti test, valuta l'aggiunta di un file di test separato denominato verify_<op_name>.mlir o verify_<your_topic>.mlir all'interno della stessa cartella.