Pytorch: RFC: Adicionar sinalizador torch.deterministic para forçar algoritmos determinísticos

Criado em 18 dez. 2018  ·  67Comentários  ·  Fonte: pytorch/pytorch

🚀 Recurso

Devemos adicionar uma variável global para forçar o PyTorch a usar algoritmos determinísticos bit a bit. Soumith sugere adicionar a bandeira a um subpacote torch.experimental porque não temos certeza sobre alguns dos detalhes.

Motivação

O determinismo bit a bit entre execuções às vezes é útil para depuração. No entanto, é difícil escrever algoritmos determinísticos eficientes para algumas operações.

Tom

Quando torch.experimental.deterministic é False (o padrão), PyTorch deve usar o algoritmo mais rápido disponível para uma determinada operação. Quando torch.experimental.deterministic é True , o PyTorch deve usar apenas algoritmos determinísticos. O PyTorch deve emitir um aviso se não tivermos um algoritmo determinístico disponível para uma determinada operação e torch.experimental.deterministic for True .

cuDNN

Já temos um sinalizador torch.backends.cudnn.deterministic para controlar as escolhas do algoritmo cuDNN. Devemos manter este sinalizador por enquanto e restringir cuDNN a algos determinísticos se torch.backends.cudnn.deterministic ou torch.experimental.deterministic for True.

Sem objetivos

Nosso objetivo é apenas o determinismo bit a bit entre execuções em máquinas com a mesma arquitetura e configuração. Por exemplo, mesmo quando torch.experimental.deterministic for True, não objetivamos o determinismo bit a bit quando qualquer um dos seguintes variar:

  • Versão PyTorch
  • Arquitetura da CPU (por exemplo, x86 com AVX vs. ARM)
  • Arquitetura de GPU (por exemplo, AMD vs. NVIDIA ou P100 vs. V100)
  • Dependências da biblioteca (por exemplo, OpenBLAS vs. MKL)
  • Número de tópicos OpenMP

Sugestões de implementação

Eu sugiro adicionar esse recurso em duas etapas. A primeira etapa é adicionar o sinalizador torch.backends.cudnn.deterministic e adicionar avisos a quaisquer operações não determinísticas. A segunda etapa é adicionar implementações determinísticas para as operações não determinísticas.

Há uma lista parcial de operações não determinísticas nos documentos do

Perguntas abertas

Como torch.experimental.deterministic interagir com a semente RNG? Deve definir uma semente padrão se nenhuma semente manual foi definida? Deve emitir um aviso se nenhuma semente manual foi definida?

cc @ezyang @gchanan @ zou3519

feature high priority determinism internals triaged

Comentários muito úteis

Olá, quero falar sobre o plano de torch.deterministic . Existem algumas perguntas de alto nível que precisamos responder:

  1. Qual é a semântica de torch.deterministic ? O que o usuário espera? O melhor esforço é realmente útil para um usuário? Se não for útil, é melhor definir torch.deterministic em termos de quais operações ele controla?
  2. Agora que temos o sinalizador torch.deterministic , faz sentido eliminar o argumento de palavra-chave deterministic= inteiramente da API voltada para o público ( bmm , estou olhando para você).
  3. Qual é o jogo final para este trabalho? Em quanto disso você (@kurtamohler) vai trabalhar, em comparação com a comunidade genérica, e quando chegarmos ao fim de sua tarefa aqui, como é um estado razoável?

Começando com (1), a documentação atual para tocch.deterministic diz:

     r"""Sets a global flag to force all operations to use a deterministic
    implementation if available. If an operation that does not have a
    deterministic implementation is called while this setting is True, the
    operation will throw a RuntimeError.

    Note that deterministic operations tend to have worse performance than
    non-deterministic operations.

Embora isso possa ser verdade para um eventual estado final, isso representa de forma imprecisa a situação atual, onde muitas operações não foram auditadas e para qualquer modelo dado, não sabemos se torch.deterministic realmente fará o que faz diz na lata e torna seu modelo determinístico / gera um erro quando você atinge o nondet. Então, basicamente, nossa implementação apresenta erros com relação a essa semântica e continuará a apresentar erros no futuro previsível. Este não é um ótimo estado para se estar.

Poderíamos alterar a documentação de torch.deterministic para melhorar isso. Algumas mudanças possíveis:

  • torch.deterministic é o melhor esforço , mas por favor, reporte os bugs se você perceber que ele não detecta algum não-determinismo
  • torch.deterministic alterna o comportamento desses operadores (e, em seguida, fornece uma lista exaustiva dos operadores que alterna)

O segundo ponto leva a (2): se torch.deterministic agora existe como uma forma de alternar o determinismo, é muito menos importante suportar o determinismo diretamente na API do usuário. Portanto, provavelmente não deveríamos ter adicionado o argumento deterministic ao bmm. Podemos considerar expor uma função interna se você quiser alternar algo diretamente, mas deterministic não deve estar disponível diretamente na função em si.

O que você acha? Acho que mudar a documentação é provavelmente a maneira mais fácil de seguir um caminho sustentável. Existem alguns outros detalhes, como a forma de preencher a lista exaustiva, mas essa semântica provavelmente faz mais sentido do que a semântica "ideal" que não será realmente verdadeira.

cc @gchanan @mruberry

Todos 67 comentários

Este é um sinal de positivo meu. O problema será principalmente como implementar isso em todos os lugares da base de código; nada pior é afirmar que somos deterministas, mas secretamente não é :)

Eu sou totalmente a favor e minha abordagem seria sinalizar operações e erros quando o determinístico está ativado e sabemos que não estão.

Acho que errar em operações não determinísticas é muito duro. Aviso parece uma experiência mais tranquila

Acho que o padrão deveria ser jogar, mas acho que poderíamos oferecer suporte a uma propriedade com vários valores lá (não determinístico está ok, avisar, lançar).

Devo admitir que realmente não vejo o caso de uso de um aviso. Quando as pessoas se preocupam com o determinístico o suficiente para ativá-lo, provavelmente esperariam o erro. Você sempre pode desligá-lo para certas chamadas para dizer que está OK com qualquer não-determinismo que esteja lá.

Erro, aviso, documentação adequada ...
Este último é obrigatório.
Aviso ou erro? Vou com um erro.

jogar parece ótimo. Concordo com Adam que dar a opção de avisar em vez de jogar parece razoável.

Obrigado por pesar. No final das contas, o principal esforço para a bandeira ternária é a própria bandeira, e isso não é difícil.
Vou adicionar um sinalizador a Context.he polvilhar (por meio de uma função de utilidade) AT_ERROR e AT_CHECK.

Olá,
Alguma notícia desta bandeira?
O determinismo é crucial.
Pela minha experiência, a versão atual permite o determinismo sobre um gpu, até uma precisão 1e-16 , usando sementes fixas. Observe que a diferença infinitesimal pode ser amplificada e divergir os resultados.

Por favor, considere o caso de multigpu também (pelo menos para um K gpus fixo, o comportamento precisa ser determinístico. Eu sou capaz de alcançar algum tipo de determinismo que falha de vez em quando por um motivo I não entendo por agora (usando a compilação noturna 1.2.0.dev20190616 ).). Estou lutando com isso agora ( 1 , 2 ).

Obrigada!

@ t-vi você está trabalhando ativamente nisso?

Eu não quero impedir você de fazer isso.

@ t-vi Desculpe se não fui claro, não estou planejando trabalhar nisso :). Só estava tentando entender se alguém estava ativamente fazendo isso.

Após quase um ano, o problema da interpolação não determinística ainda não foi resolvido.

Espero que a comunidade adicione esse recurso :)

Talvez uma interpolação determinística ajudasse muito os usuários.

~ Eu realmente não anunciei ainda, mas dado que parece haver mais interesse do usuário do que recursos do desenvolvedor alocados, eu tenho isso listado como um projeto no qual você pode votar na minha página de patrocínio do github quando eu configurá-lo.
Tenho certeza de que poderíamos fazer um bom progresso até o final do ano e a interpolação certamente é uma das coisas que tenho um plano para consertar (semelhante ao pseudocódigo para dobra que está em algum lugar dos problemas), mas simplesmente não está no topo da minha lista de prioridades. ~
Acabou por não ser interessante.

uma interpolação determinística será de grande ajuda. link

Prioridade de colisão, esp para CUDA, com base no feedback do usuário

Fico feliz que esteja sendo consertado, obrigado!

@ t-vi para ser justo, não acho que "prioridade de rejeição" seja equivalente a "está sendo consertada" :).

Ansioso pelas soluções!

colesbury mencionou que uma razão assassina para algoritmos determinísticos não é porque o determinismo é realmente o problema, mas que você pode descartá-lo quando liga isso;)

Como torch.experimental.deterministic interagir com a semente RNG? Deve definir uma semente padrão se nenhuma semente manual foi definida? Deve emitir um aviso se nenhuma semente manual foi definida?

Eu sugiro não definir uma semente se nenhuma tiver sido definida pelo usuário. Por um lado, porque acopla duas interfaces que não são necessárias (eu acho que usuários que se preocupam com determinismo entenderão RNGs muito bem). Mais importante, isso é muito difícil de fazer com segurança; pode-se usar um RNG em aplicativos multiprocessos / encadeados, ter outras torch.Generator subclasses, usar numpy.random também, etc.

Não tenho certeza sobre um aviso, apenas se houver um lugar lógico para defini-lo (por exemplo, você está forçando a propagação antes de determinism=True vez de no mesmo módulo / função onde um RNG é usado?).

Estou apenas curioso para saber que quando defino torch.backends.cudnn.deterministic=True , o operador de interpolação ainda não pode ser determinístico. A interpolação de pytorch não usa cudnn?

Pode não ser. Você pode nvprof sua execução de interpolar para verificar com certeza.

Estou me perguntando se devemos ou não continuar fornecendo os deterministic argumentos em chamadas de função, uma vez que torch.experimental.deterministic é implementado. Talvez devêssemos, porque um usuário pode preferir determinismo para algumas operações e velocidade para outras operações.

Se mantivermos os argumentos, o que acontecerá se torch.experimental.deterministic e o sinalizador deterministic uma função se oporem. torch.experimental.deterministic = True significa "usar determinismo em todos os casos, não importa o quê", ou deve significar "usar determinismo como um valor padrão, mas se o argumento deterministic for especificado em uma chamada de função, então use essa configuração para essa chamada de função específica. " Em outras palavras, como o código abaixo deve ser tratado? Alguém sabe como a bandeira torch.backends.cudnn.deterministic age em uma situação semelhante?

torch.experimental.deterministic = True
torch.some_operation(deterministic=False)

@kurtamohler Boa pergunta. Acho que a solução mais fácil é torná-lo bool? deterministic=None e, em seguida, interpretar None como "respeitar torch.experimental.deterministic " e usar exatamente o que o usuário solicitou.

Nós meio que temos uma situação semelhante com convolução, mas da forma como foi feito lá é que havia um convolution sem benchmark argumento, e então um _convolution com um argumento explícito benchmark.

Acho que qualquer uma dessas soluções seria aceitável; no entanto, a abordagem de convolução tem um benefício adicional de não vazar o sinalizador deterministic interno para a API visível ao usuário (a menos que eles usem uma API interna).

Qual é a justificativa para "Eu quero ser determinista em todos os lugares, mas _não neste operador em particular_"? Este é realmente um caso de uso comum o suficiente para garantir a adição de uma entrada extra para muitos de nossos operadores (e a maioria dos mais complexos)? IMO seria melhor fornecer gerenciadores de contexto para alternar o determinismo.

@apaszke , sim, acho que você está certo que seria melhor apenas usar gerenciadores de contexto para alternar o determinismo. Eu não diria que devemos adicionar o argumento deterministic a quaisquer operadores, mas alguns operadores já o têm. Seria melhor remover tudo isso e quebrar o BC, ou seria melhor mantê-los por perto e permitir que substituam torch.experimental.deterministic ?

Eu diria que devemos removê-lo ou torná-lo privado, pelo menos (ou seja, prefixo de sublinhado ou sth).

Estou me perguntando se o recurso determinístico para a função de interpolar está fechado e não será implementado?

Não, somos receptivos a versões determinísticas de TODAS as funções em PyTorch

@ezyang qual versão do pytorch tem a função F.interpolate determinística? está começando do pytorch 1.6? ou está disponível na última versão estável (1.5)? ou tenho que baixar e instalar o Pytorch da fonte?

Eu ficaria feliz em começar a trabalhar nisso

O commit acima apenas adiciona o sinalizador, ele não afeta nenhuma operação ainda. Eu apreciaria se alguém pudesse dedicar alguns minutos para dar uma olhada e me avisar se eu fiz algo incorreto ou se algo pudesse ser melhorado até agora. Eu baseei isso em como torch.backends.cudnn.deterministic é implementado.

Parece certo, mas acho que a nomenclatura interna não deve incluir experimental (já que, aparentemente, algum dia você deseja torná-la não experimental e isso não deve envolver a necessidade de renomear todos os bits de implementação!)

@ezyang , sim, faz sentido, vou renomear.

Eu adicionei torch.experimental.deterministic_error_level , semelhante ao que @ t-vi fez em seu trabalho anterior sobre esta questão. deterministic_error_level controla o comportamento de erro / aviso se deterministic == True e uma determinada função não tiver uma implementação determinística. Pode ser definido como 2 (erro), 1 (aviso) ou 0 (silencioso).

Se o usuário o definir com qualquer outro valor, desejo lançar uma exceção de tempo de execução Python capturável. Normalmente, eu usaria TORCH_CHECK() para esse tipo de comportamento, mas, neste caso, a exceção não pode ser capturada e não tenho certeza do porquê. Aqui está o link de TORCH_CHECK() call:

Isso é o que acontece quando a verificação falha:

>>> import torch
>>> try:
...     torch.experimental.deterministic_error_level=50
... except:
...     print('exception caught')
... 
terminate called after throwing an instance of 'c10::Error'
  what():  error level 50 is invalid, must be one of 0: None, 1: Warn, or 2: Error
Exception raised from longToErrorLevel at ../aten/src/ATen/Context.cpp:85 (most recent call first):
frame #0: c10::Error::Error(c10::SourceLocation, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) + 0x58 (0x7f53e2cc0878 in /work/kurtamohler/development/pytorch-deterministic-flag/torch/lib/libc10.so)
frame #1: at::Context::longToErrorLevel(long) + 0x122 (0x7f53f6d61a82 in /work/kurtamohler/development/pytorch-deterministic-flag/torch/lib/libtorch_cpu.so)
frame #2: THPModule_setDeterministicErrorLevel(_object*, _object*) + 0x31 (0x7f53fb5625d1 in /work/kurtamohler/development/pytorch-deterministic-flag/torch/lib/libtorch_python.so)
<omitting python frames>
frame #23: __libc_start_main + 0xe7 (0x7f5432d62b97 in /lib/x86_64-linux-gnu/libc.so.6)

Aborted (core dumped)

Se alguém souber como posso consertar isso, me avise.

@kurtamohler está THPModule_setDeterministicErrorLevel falta HANDLE_TH_ERRORS / END_ HANDLE_TH_ERRORS macros? Eles são necessários para capturar a exceção C ++ e traduzi-la em um retorno de erro do Python.

Ah é isso, obrigado @colesbury!

Estou começando a adicionar o alerta não determinístico para todos os chamadores de atomicAdd . Percebi que alguns chamadores usam apenas atomicAdd em certos casos. Por exemplo, adaptive_avg_pool3d_backward só usa se (isizeW%osizeW != 0) || (isizeH%osizeH != 0) || (isizeT%osizeT != 0) for verdadeiro. Devo apenas alertar nesses casos e tentar transmiti-los na mensagem de erro, ou seria correto apenas alertar sempre que essas funções forem chamadas, quer atomicAdd acabe ou não sendo usado?

Provavelmente é mais fácil de implementar e entender se você alerta incondicionalmente.

@ngimel , estive pensando em como usar CUBLAS_WORKSPACE_CONFIG para garantir o uso de stream determinístico e acho que há duas abordagens principais que devem ser consideradas.

Se alguém estiver usando uma das versões CUDA afetadas (10.2 ou superior no momento) e torch.set_deterministic(True) for chamado, use std::getenv para certificar-se de que CUBLAS_WORKSPACE_CONFIG seja :16:8 ou :4096:8 . Caso contrário, faça (1) ou (2):

  1. Lança um erro informando ao usuário para definir a variável apropriadamente.

  2. Defina automaticamente a variável com putenv ( _putenv no Windows). No entanto, existem algumas outras decisões de design associadas a isso. Devemos escolher :16:8 (desempenho mais baixo, mas menos uso de memória) ou :4096:8 (desempenho mais alto, mas mais uso de memória)? Além disso, se o usuário definir a variável para algum outro valor não determinístico, teríamos que acompanhar o valor original e restaurá-lo se torch.set_deterministic(False) for chamado, ou poderíamos lançar um erro informando ao usuário que eles precisam remover a configuração da variável ou algum outro esquema.

Além disso, não sei se definir a variável enquanto o aplicativo já está em execução terá algum efeito, portanto, não tenho certeza se a opção (2) é possível. A variável só pode ser verificada uma vez, quando o tempo de execução CUDA é iniciado ou quando um identificador cuBLAS é criado. Não consegui encontrar informações sobre isso, então provavelmente terei que descobrir experimentalmente (terei que usar um reprodutor de uso de fluxo não determinístico para escrever um teste de qualquer maneira, então vou examinar isso) . Também procurei uma chamada de API, em vez de usar a variável de ambiente, mas o CUDA não parece oferecer uma.

Você tem uma opinião forte sobre qual opção seria melhor? A opção (2) provavelmente seria mais amigável, mas possivelmente menos transparente do que a opção (1).

Não sei se definir a variável enquanto o aplicativo já está em execução terá algum efeito

Para acompanhar esta questão, definir a variável de ambiente dentro de um script pytorch não parece afetar o determinismo do fluxo CUDA. Modifiquei o script de https://github.com/pytorch/pytorch/issues/39849 para executar várias vezes e comparar as estatísticas de treinamento para verificar o comportamento não determinístico. Ele tenta definir CUBLAS_WORKSPACE_CONFIG=:4096:8 para garantir o uso de fluxo determinístico: https://github.com/kurtamohler/pytorch-perf-test-scripts/blob/master/nondeterministic_alert/cuda_stream_nondeterminism.py

Executá-lo mostra que não obtemos comportamento determinístico ao definir a variável dentro do script:

$ python cuda_stream_nondeterminism.py 
Before setting var: not deterministic
After setting var: not deterministic
After restoring old var: not deterministic

Mas executá-lo com a variável de ambiente definida fora do script o torna determinístico:

$ CUBLAS_WORKSPACE_CONFIG=:4096:8 python cuda_stream_nondeterminism.py 
Before setting var: possibly deterministic
After setting var: possibly deterministic
After restoring old var: possibly deterministic

Observe, ele imprime "possivelmente determinístico" porque eu só executo a função de treinamento 5 vezes e é possível ter sorte mesmo se o comportamento não for realmente determinístico.

Talvez se eu pudesse reinicializar o fluxo cuda, isso o forçaria a honrar a variável CUBLAS_WORKSPACE_CONFIG alterada. Eu gostaria de tentar isso, mas não sei como ou se é possível fazer isso em tempo de execução. Se alguém souber, por favor me avise.

Descobri que posso criar e usar um novo stream com:

with  torch.cuda.stream(torch.cuda.Stream()):

Mas o novo fluxo não respeita a configuração da variável de ambiente alterada. Eu também encontrei torch.cuda.init() , mas infelizmente, isso é um no-op se o cuda já foi inicializado.

Portanto, a menos que possamos pensar em outra coisa para tentar, parece que provavelmente não podemos alterar a configuração do espaço de trabalho automaticamente, então talvez tenhamos que lançar um erro dizendo ao usuário para defini-la.

Sim, definir a variável de ambiente após o contexto cuda ter sido inicializado não tem efeito, então infelizmente é tudo ou nada solução. Lançar um erro dizendo ao usuário para defini-lo parece razoável.

Atualmente, não parece ser possível verificar a versão CUDA de um arquivo compilado não nvcc, então acredito que terei que adicionar isso a aten/src/ATen/cuda/detail/CUDAHooks.h (verificar a versão cuDNN faz parte dessa interface) . Se alguém souber de algo melhor, por favor me avise.

O commit acima adiciona o erro. Mas preciso descobrir o que fazer com os testes de unidade agora. Existem dois problemas:

  • Para testar se o erro está sendo gerado no caso correto (cuda> = 10.2 e CUBLAS_WORKSPACE_CONFIG não está definido corretamente), a infraestrutura de teste deve ser capaz de alterar automaticamente a variável de ambiente antes de executar um teste
  • Para garantir que os testes torch.set_deterministic não sejam interrompidos, precisaríamos definir CUBLAS_WORKSPACE_CONFIG automaticamente de maneira adequada. Poderíamos potencialmente apenas definir essa variável por padrão em todos os trabalhos de CI que usam cuda> = 10,2.

Descobri que posso definir variáveis ​​de ambiente a partir de um script Python e recarregar o módulo tocch para fazê-lo respeitar o novo valor:

>>> import torch
>>> torch.set_deterministic(True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/work/kurtamohler/development/pytorch-deterministic-flag-cuda-env-var/torch/__init__.py", line 306, in set_deterministic
    _C._set_deterministic(d)
RuntimeError: To enable deterministic behavior with CUDA >= 10.2, you must set environment variable CUBLAS_WORKSPACE_CONFIG=:4096:8 or CUBLAS_WORKSPACE_CONFIG=:16:8. For more information, go to https://docs.nvidia.com/cuda/cublas/index.html#cublasApi_reproducibility
>>> import os
>>> os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8'
>>> from importlib import reload
>>> torch = reload(torch)
>>> torch.set_deterministic(True)

Não sei se recarregar a tocha também fará com que o CUDA respeite essa alteração, mas pelo menos isso nos dá uma maneira de testar a unidade para a mensagem de erro. Embora eu deva perguntar, pode haver algum problema em recarregar o módulo da tocha dentro de um teste de unidade?

EDIT: Acontece que eu não preciso recarregar a tocha para fazer ver a variável de ambiente alterada. Além disso, recarregar após alterar a variável não afeta o tempo de execução CUDA.

O commit acima aborda todas as preocupações que mencionei em meu comentário anterior. Eu adicionei um decorador para envolver qualquer teste de API que chame torch.set_deterministic() , definindo temporariamente CUBLAS_WORKSPACE_CONFIG=:4096:8 apenas se necessário. Ele também restaura o sinalizador determinístico e as configurações CUBLAS_WORKSPACE_CONFIG para o que eram antes da execução do teste.

Percebi que o documento de reprodutibilidade menciona que o comportamento CuDNN determinístico requer:

torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

Alguém neste tópico sabe o que benchmark é exatamente e por que torch.backends.cudnn.deterministic = True por si só não é suficiente?

Podemos querer forçar benchmark a ser desligado se torch.is_deterministic() == True . Em outras palavras, em vez de passar ctx.benchmarkCuDNN() diretamente para at::_convolution() , talvez devesse ser ctx.benchmarkCuDNN() && !ctx.deterministic() nesta linha: https://github.com/pytorch/pytorch/blob/ master / aten / src / ATen / native / Convolution.cpp # L602

Se não fizermos essa alteração, parece que as pessoas que usam set_deterministic e CuDNN terão que fazer isso:

torch.set_deterministic(True)
torch.backends.cudnn.benchmark = False

O que significa que set_deterministic() si só não cobriria tudo, o que é confuso na minha opinião.

cc @ezyang @colesbury @ t-vi @ngimel

Ao encontrar uma nova configuração de convolução, benchmark=True executa todas as implementações cudnn disponíveis e escolhe uma mais rápida, armazenando em cache a implementação que escolheu, para que todas as chamadas subsequentes à convolução com os mesmos parâmetros a utilizem. Portanto, se deterministic também estiver definido como True os resultados serão determinísticos enquanto esse cache persistir, ou seja, enquanto você estiver no mesmo processo. Se houver implementações com tempo de execução fechado, da próxima vez que você iniciar o processo e executar o benchmarking novamente, outra implementação pode vencer e os resultados (embora ainda determinísticos no sentido descrito acima) serão diferentes da execução anterior. Portanto, para garantir o determinismo entre as execuções, você deve desativar o benchmarking.

Eu vejo. Portanto, talvez apenas o determinismo em processo, não o determinismo de processo cruzado, importe para algumas aplicações, então as pessoas podem achar útil ainda ser capaz de usar benchmarking se definirem torch.set_deterministic(True) . Nesse caso, não devo mudar o comportamento atual. Contanto que eu atualize os documentos para deixar isso claro, não vejo nenhum problema nisso.

Criei uma página wiki para ajudar os contribuidores do PyTorch a adicionar suporte para torch.set_deterministic() : https://github.com/pytorch/pytorch/wiki/How-to-support-%60torch.set_deterministic ()% 60-in Operadores PyTorch

Quaisquer melhorias são bem-vindas.

Além disso, eu não tinha certeza se a seção "Funções atualmente sem suporte" deveria estar neste wiki ou se seria melhor como um novo problema do github (a página do wiki poderia ter um link para ele). Alguém tem preferência?

Olá, quero falar sobre o plano de torch.deterministic . Existem algumas perguntas de alto nível que precisamos responder:

  1. Qual é a semântica de torch.deterministic ? O que o usuário espera? O melhor esforço é realmente útil para um usuário? Se não for útil, é melhor definir torch.deterministic em termos de quais operações ele controla?
  2. Agora que temos o sinalizador torch.deterministic , faz sentido eliminar o argumento de palavra-chave deterministic= inteiramente da API voltada para o público ( bmm , estou olhando para você).
  3. Qual é o jogo final para este trabalho? Em quanto disso você (@kurtamohler) vai trabalhar, em comparação com a comunidade genérica, e quando chegarmos ao fim de sua tarefa aqui, como é um estado razoável?

Começando com (1), a documentação atual para tocch.deterministic diz:

     r"""Sets a global flag to force all operations to use a deterministic
    implementation if available. If an operation that does not have a
    deterministic implementation is called while this setting is True, the
    operation will throw a RuntimeError.

    Note that deterministic operations tend to have worse performance than
    non-deterministic operations.

Embora isso possa ser verdade para um eventual estado final, isso representa de forma imprecisa a situação atual, onde muitas operações não foram auditadas e para qualquer modelo dado, não sabemos se torch.deterministic realmente fará o que faz diz na lata e torna seu modelo determinístico / gera um erro quando você atinge o nondet. Então, basicamente, nossa implementação apresenta erros com relação a essa semântica e continuará a apresentar erros no futuro previsível. Este não é um ótimo estado para se estar.

Poderíamos alterar a documentação de torch.deterministic para melhorar isso. Algumas mudanças possíveis:

  • torch.deterministic é o melhor esforço , mas por favor, reporte os bugs se você perceber que ele não detecta algum não-determinismo
  • torch.deterministic alterna o comportamento desses operadores (e, em seguida, fornece uma lista exaustiva dos operadores que alterna)

O segundo ponto leva a (2): se torch.deterministic agora existe como uma forma de alternar o determinismo, é muito menos importante suportar o determinismo diretamente na API do usuário. Portanto, provavelmente não deveríamos ter adicionado o argumento deterministic ao bmm. Podemos considerar expor uma função interna se você quiser alternar algo diretamente, mas deterministic não deve estar disponível diretamente na função em si.

O que você acha? Acho que mudar a documentação é provavelmente a maneira mais fácil de seguir um caminho sustentável. Existem alguns outros detalhes, como a forma de preencher a lista exaustiva, mas essa semântica provavelmente faz mais sentido do que a semântica "ideal" que não será realmente verdadeira.

cc @gchanan @mruberry

@ zou3519 cruzou com o Q também em https://github.com/pytorch/pytorch/pull/38683#issuecomment -662590937

Fico feliz que você tenha levantado essas questões @ezyang , @ zou3519 e @mruberry. Concordo que a documentação que escrevi é uma representação falsa do estado atual.

Gosto da ideia de listar exaustivamente todas as funções que torch.set_deterministic() afeta, para que não estejamos mentindo para o usuário. Obrigado por adicionar isso ao 1.6.0, @ zou3519.

Concordo que não devemos oferecer a configuração deterministic como argumentos de função diretos.

Quanto ao jogo final, fico feliz em continuar trabalhando nisso pelo tempo que for necessário, mas deve ser configurado para que qualquer pessoa possa aprender rapidamente como ajudar.

No longo prazo, acho que fornecer uma lista exaustiva de funções afetadas é uma decisão válida, mas não acho que a estratégia por si só maximizaria a utilidade do sinalizador determinístico. Podemos categorizar funções (em um ambiente específico) assim:

  1. Determinístico
  2. Não determinístico por padrão, mas tem suporte para o sinalizador determinístico (erro ou implementação alternativa)
  3. Não determinístico e não tem suporte para o sinalizador determinístico

Claro que o caso ideal é eliminar completamente a categoria 3, e então a lista de funções da categoria 2 seria suficiente. No entanto, as funções da categoria 3 ainda existirão por um período de tempo significativo (ou talvez para sempre, se nem todos os contribuidores estão cientes da questão do determinismo, ou um commit remove acidentalmente o determinismo para uma função, etc.). Portanto, mesmo que tenhamos uma lista exaustiva de todas as funções da categoria 2, o usuário não tem uma maneira simples de saber se uma função que não aparece na lista é determinística ou não (pode ser da categoria 1 ou 3). Por exemplo, torch.add não aparece na lista, então como o usuário sabe que é determinístico?

Talvez possamos pensar em manter uma lista de funções da categoria 3 também. Mas manter essas listas manualmente seria muito difícil por muitos motivos, então eu me pergunto se poderíamos automatizar de alguma forma. Poderíamos potencialmente configurar um trabalho de CI que executa testes de determinismo em todas as funções. Não é possível provar 100% indutivamente que uma função é determinística, e uma função não determinística pode às vezes dar o mesmo resultado várias vezes se não tivermos sorte. Porém, quanto mais frequentemente executarmos esses testes, mais confiantes poderemos nos tornar sobre a categoria da qual cada função faz parte.

Há também a questão de como transmitir ao usuário de forma mais eficiente tudo o que sabemos e não sabemos sobre cada função e cada plataforma. Talvez pudéssemos fazer uma tabela de todas as funções das categorias 2 e 3 em cada plataforma. Seria bom se os testes de determinismo pudessem verificar automaticamente se essa tabela está correta.

Apenas brainstorming, talvez essas ideias sejam mais difíceis do que valem. Um plano mais pragmático poderia ser significativamente mais sustentável, mesmo que menos ideal.

torch.add determinístico?

import torch
n = 512
device = 'cuda'
a = torch.arange(n**3, device=device, dtype=torch.float32)
a = a.reshape((n, n, n))
b = torch.arange(n**3, device=device, dtype=torch.float32)
b = b.reshape((n, n, n))
out_zero = torch.zeros((n, n, n), device=device)
out_zero = out_zero.set_(out_zero.storage(), storage_offset=0, size=a.size(), stride=(1,1,1))
out_one = torch.zeros((n, n, n), device=device)
out_one = out_one.set_(out_one.storage(), storage_offset=0, size=a.size(), stride=(1,1,1))

torch.add(a, b, out=out_zero)
torch.add(a, b, out=out_one)
(out_zero == out_one).all()
: tensor(False, device='cuda:0')

Provavelmente deveríamos documentar que tensores sobrepostos violam qualquer contrato de determinismo que pretendemos.

Listar as operações afetadas por um sinalizador de "determinismo" parece bom. Recuando um pouco, no entanto, parece que estamos realmente falando sobre duas coisas:

  • Solicitando versões determinísticas de operações, se disponíveis ( use_deterministic ?)
  • Aviso se uma operação não for determinística

Uma bandeira para a primeira coisa parece direta. O segundo, entretanto, é um pouco mais complicado. Preocupo-me com o fato de ser difícil dizer se as operações de bibliotecas matemáticas como oneDNN, cuDNN e MAGMA são determinísticas, especialmente entre versões e hardware. Você tem uma ideia da melhor forma de resolver isso, @kurtamohler? Talvez pudéssemos avisar sobre todas as operações nativas não determinísticas e também avisar quando chamadas de biblioteca matemática foram feitas? Advertir uma vez por processo não deve ser tão intrusivo.

Essa abordagem aos avisos exigiria a revisão de muitos algoritmos e sites de chamadas antes de entrar no ar, mas não precisa bloquear o sinalizador para selecionar algoritmos determinísticos, se estiverem disponíveis.

(Uma terceira coisa em discussão é a melhor maneira de apresentar a seleção de algo determinístico (por meio de um sinalizador global ou como kwargs em funções), mas acho que podemos atrasar essa discussão até determinarmos um plano para o (s) sinalizador (es)?)

Acho que não devemos deixar o perfeito ser inimigo do bom aqui. Não sei quando era 100% seguro usar tensores sobrepostos com PyTorch, e minha impressão é que não é comum que as pessoas os usem.

Minha impressão dos fóruns é que a maioria das pessoas fica surpresa por executar algo duas vezes e obter gradientes diferentes disso, na maioria das vezes por causa de uma das funções nativas do PyTorch usando atomicAdd.
Se recebermos avisos sobre isso, cobrimos a maioria dos casos sobre os quais as pessoas estão se perguntando. Algo que parece metade disso é, na verdade, aumento de escala para trás.

Acho que devemos deixar claro que este é o melhor esforço no que diz respeito às libs externas e que adicionamos avisos à medida que conhecemos os problemas, mas minha impressão é que nossos kernels nativos na verdade são o que mais importa.

Não sei quando era 100% seguro usar tensores sobrepostos com PyTorch, e minha impressão é que não é comum que as pessoas os usem.

Sim, e quaisquer programas que o façam podem ser razoavelmente classificados como errados. Só quis dizer que devemos ter o cuidado de documentar qualquer contrato que façamos para essas bandeiras.

Acho que devemos deixar claro que este é o melhor esforço no que diz respeito às bibliotecas externas e que adicionamos avisos à medida que conhecemos os problemas ...

O doc pode dizer algo como "chamadas de biblioteca matemática que são conhecidas como não determinísticas ..."?

Eu concordo com @ t-vi (e eu realmente gosto da observação de que metade do não-determinismo relatado está aumentando para trás). Em particular, acho que um estado onde temos funções parcialmente documentadas que são conhecidas como não determinísticas (ou até mesmo algumas funções parcialmente documentadas como determinísticas) é estritamente melhor do que aquele em que não fornecemos nenhuma indicação - a coisa principal é não alegar apoiar coisas que não apoiamos! Concordo que é uma atividade útil pensar sobre como alguém poderia fazer o teste de determinismo, mas acho que isso é uma atividade ortogonal para sinalizar APIs que são obviamente não determinísticas.

Como muitas ideias estão surgindo, deixe-me apenas comentar com meus pensamentos específicos sobre algumas delas:

  1. "Talvez pudéssemos pensar em manter uma lista de funções da categoria 3 também." Isso parece muito trabalhoso. Acho que provavelmente só vale a pena para funções em que fizemos algumas acomodações explicitamente para o determinismo (mais provavelmente, funções que suportam o sinalizador determinístico)
  2. "Poderíamos potencialmente configurar um trabalho de CI que execute testes de determinismo em todas as funções." Acho que algo assim teria que ser feito com muito cuidado, porque por sua própria natureza é um teste para algo que não é determinístico, e isso significa que o teste de determinismo em si é "fragmentado" (passará algumas vezes e falhará em outras) . Nossas ferramentas de relatório de IC não lidam muito bem com situações como essa.
  3. "A segunda, entretanto, é um pouco mais complicada. Preocupo-me que seja difícil dizer se as operações de bibliotecas matemáticas como oneDNN, cuDNN e MAGMA são determinísticas, especialmente entre versões e hardware." Devemos nos esforçar ao máximo. Em muitos casos, a biblioteca matemática especifica explicitamente em sua documentação que eles são determinísticos ou não, e devemos simplesmente relatar fielmente o que os documentos dizem
  4. "Talvez pudéssemos avisar sobre todas as operações nativas não determinísticas e também avisar quando as chamadas da biblioteca matemática foram feitas também?" Não acho que devemos fazer isso. Quando alertamos sobre o não-determinismo, deveria ser porque o não-determinismo ESTÁ acontecendo, não que PODE estar acontecendo. Se você exagerar, as pessoas começarão a ignorar os avisos.

Não acho que devemos nos preocupar com determinismo de hardware / versão cruzada - boa sorte com isso.

Quando alertamos sobre o não-determinismo, deveria ser porque o não-determinismo ESTÁ acontecendo, não que PODE estar acontecendo. Se você exagerar, as pessoas começarão a ignorar os avisos.

parece complicado. Por exemplo, e se eu estiver executando algum op e a implementação do PyTorch for determinística, mas alguma extensão substituiu algo (por meio de uma tecla de envio, função de tocha ou outro) e agora eu não sei. Se essa é realmente a fonte do meu não determinismo, parece uma chatice não ser avisado.

Se essa é realmente a fonte do meu não determinismo, parece uma chatice não ser avisado.

Claro, mas o usuário também não poderia nos envolver em suas travessuras não determinísticas e, então, é claro que você não esperaria ser avisado;)

Acredito que podemos encerrar esse problema agora, pois a API de sinalização existe e está bem documentada.

@kurtamohler Trabalho incrível. Obrigada.

Isso significa que poderíamos usar torch.manual_seed(111) para definir tudo determinístico, incluindo a operação interpolation ?

Não. Dê uma olhada na nota sobre reprodutibilidade / aleatoriedade .
Até agora, temos infraestrutura, marcamos as fontes conhecidas sobre não determinismo e melhoramos bastante a documentação para que você possa saber o que está acontecendo.
Se você atingir operações não determinísticas, ainda estará sem sorte, mas agora é mais razoável trabalhar nisso.

A interpolação em particular parece algo que poderia ser tornado determinístico escrevendo um kernel não tão complicado para o reverso.

@ t-vi Olá, Agora que o pytorch 1.7 foi lançado, o kernel de interpolação retroativa foi atualizado?

Portanto, os kernels de upsampling de CUDA e para trás estão em aten/src/ATen/native/cuda/UpSample* . Um grep sugere que linear, bilinear, cúbico têm reverso não determinístico (eles têm um marcador de aviso), mas o mais próximo não tem.
No entanto ,

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

NgPDat picture NgPDat  ·  3Comentários

kdexd picture kdexd  ·  3Comentários

cdluminate picture cdluminate  ·  3Comentários

eliabruni picture eliabruni  ·  3Comentários

szagoruyko picture szagoruyko  ·  3Comentários