- Adicionar operações assíncronas ao HLO é complicado (por exemplo,
all-reduce-start
eall-reduce-done
). - As divisões de início e conclusão podem ser inadequadas para alguns dos usos assíncronos casos de uso diferentes.
Para atingir a primeira deficiência, propomos introduzir um último conjunto de novas
códigos de operação assíncronas: kAsyncStart
, kAsyncUpdate
e kAsyncDone
. A ideia
é criar um código de operação assíncrono genérico que possa ajustar qualquer instrução HLO.
A operação real que será executada de forma assíncrona será codificada usando
uma computação chamada que tem somente a instrução como raiz e qualquer
parâmetros para entradas. A manipulação de buffer de entrada/saída em trânsito e atribuição de alias
podem ser compartilhados para qualquer operação assíncrona. As instruções de início assíncrono
forma de saída será uma tupla dos operandos de entrada, valores de saída e quaisquer
estado intermediário necessário para a async-update
ou a async-done
instruções.
%async_op {
%param0 = f32[64] parameter(0)
ROOT %op = f32[32] op(f32[64] %param0), op_specific_attr=”foo”
}
%async-start = (f32[64], f32[32], s32[]) async-start(f32[64] %operand),
calls=%async_op
%async-done = f32[32] async-done((f32[64], f32[32], s32[]) %async-start)
Na representação acima, apenas async-start
tem um cálculo chamado, já que
é trivial descobrir o que async-done
faz seguindo o operando para
encontrar o async-start
correspondente para encontrar o cálculo chamado.
Observe também
que o primeiro elemento na tupla de saída dos aliases async-start
com a
operando para que o buffer permaneça ativo até pelo menos a instrução async-done.
Da mesma forma, o segundo elemento cria um alias com a saída de async-done
e o
o terceiro elemento é o estado de contexto usado para acompanhar os
operação assíncrona. Essa representação também aceita vários tensores em
a entrada e/ou saída da operação assíncrona e a atribuição de alias funciona da mesma
maneira:
%async_op {
%param0 = f32[64] parameter(0)
%param1 = f32[64] parameter(1)
ROOT %op = (f32[32], f32[32]) op(f32[64] %param0, f32[64] %param1),
op_specific_attr=”foo”
}
%async-start = ((f32[64], f32[64]), (f32[32], f32[32]), s32[])
async-start(f32[64] %operand0, f32[64] %operand1),
calls=%async_op
%async-done = (f32[32], f32[32]) async-done(%async-start)
Além disso, a operação pode ser decomposta em zero ou mais async-update
.
etapas que realizam cálculos intermediários. A atribuição de alias de entrada/saída funciona
da mesma forma com a instrução async-update
, e cada async-start
e
As instruções de async-update
precisam ter um usuário que seja outro
async-update
ou um async-done
:
%async_op {
%param0 = f32[64] parameter(0)
ROOT %op = f32[32] op(f32[64] %param0), op_specific_attr=”foo”
}
%async-start = (f32[64], f32[32], s32[]) async-start(f32[64] %operand),
calls=%async_op
%async-update0 = (f32[64], f32[32], s32[]) async-update(
(f32[64], f32[32], s32[]) %async-start)
%async-update1 = (f32[64], f32[32], s32[]) async-update(
(f32[64], f32[32], s32[]) %async-update0)
%async-done = f32[32] async-done((f32[64], f32[32], s32[]) %async-update1)
Açúcar de sintaxe
Como há um cálculo separado para definir a operação que será realizada de forma assíncrona é um pouco complicado, também propomos uma sintaxe mais leve imprime e analisa automaticamente as operações assíncronas como se fossem de primeira classe códigos de operação. A ideia é tratar os sufixos “-start”, “-update” e “-done” especialmente criando automaticamente a computação e a instrução (sem a ) ao analisar. Por exemplo, o snippet de código acima pode ter uma formatação para o seguinte, e os dois podem ser analisados na mesma representação:
%op-start = (f32[64], f32[32], s32[]) op-start(f32[64] %operand),
op_specific_attr=”foo”
%op-update0 = (f32[64], f32[32], s32[]) op-update(
(f32[64], f32[32], s32[]) %op-start),
op_specific_attr=”foo”
%op-update1 = (f32[64], f32[32], s32[]) op-update(
(f32[64], f32[32], s32[]) %op-update0)
%op-done = f32[32] op-done((f32[64], f32[32], s32[]) %op-update1)
Para não criar ambiguidades, o verificador não permitirá que uma operação
ser encapsulado com async-start se definirmos explicitamente um opcode para esse
com os sufixos "-start" e/ou "-done". Esta também é uma fuga
surgir caso haja alguma instrução que exija tratamento de nível HLO que
não se encaixa no modelo descrito acima (por exemplo, a entrada/saída de alias
reservas). Inicialmente, copy-start
/copy-done
collective-permute-start
/collective-permute-done
etc. vão continuar usando
seus respectivos códigos de operação de primeira classe, em vez dos novos
async-start
/async-done
opcodes até limparmos o código para removê-los
de operações "-start"/"-done".