Evalml: Tempos limite de teste de unidade (instabilidade de Dask)

Criado em 9 jun. 2021  ·  11Comentários  ·  Fonte: alteryx/evalml

No momento, estamos vendo os testes de unidade atingirem o limite de Ações do GH de 6 horas. Isso não é bom por motivos óbvios.

3,8 deps de núcleo 6 horas limite (em andamento)
build_conda_pkg, 3.8 core deps, 3.7 não core deps 6 hr timeout (em andamento)
3,7 deps não essenciais 6 horas de tempo limite
3,8 deps não essenciais 6 horas de tempo limite
3,7 dependências não essenciais 1,5 horas
build_conda_pkg
3,7 dependências não essenciais
3,8

blocker bug testing

Comentários muito úteis

Agora estou vendo o seguinte rastreamento de pilha em build_conda_pkg

[gw3] linux -- Python 3.7.10 $PREFIX/bin/python

X_y_binary_cls = (          0         1         2   ...        17        18        19
0  -0.039268  0.131912 -0.211206  ...  1.976989  ...ns], 0     0
1     0
2     1
3     1
4     1
     ..
95    1
96    1
97    1
98    1
99    0
Length: 100, dtype: int64)
cluster = LocalCluster(15c4b3ad, 'tcp://127.0.0.1:45201', workers=0, threads=0, memory=0 B)

    def test_submit_training_jobs_multiple(X_y_binary_cls, cluster):
        """Test that training multiple pipelines using the parallel engine produces the
        same results as the sequential engine."""
        X, y = X_y_binary_cls
        with Client(cluster) as client:
            pipelines = [
                BinaryClassificationPipeline(
                    component_graph=["Logistic Regression Classifier"],
                    parameters={"Logistic Regression Classifier": {"n_jobs": 1}},
                ),
                BinaryClassificationPipeline(component_graph=["Baseline Classifier"]),
                BinaryClassificationPipeline(component_graph=["SVM Classifier"]),
            ]

            def fit_pipelines(pipelines, engine):
                futures = []
                for pipeline in pipelines:
                    futures.append(
                        engine.submit_training_job(
                            X=X, y=y, automl_config=automl_data, pipeline=pipeline
                        )
                    )
                results = [f.get_result() for f in futures]
                return results

            # Verify all pipelines are trained and fitted.
            seq_pipelines = fit_pipelines(pipelines, SequentialEngine())
            for pipeline in seq_pipelines:
                assert pipeline._is_fitted

            # Verify all pipelines are trained and fitted.
>           par_pipelines = fit_pipelines(pipelines, DaskEngine(client=client))

evalml/tests/automl_tests/dask_tests/test_dask_engine.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
evalml/tests/automl_tests/dask_tests/test_dask_engine.py:94: in fit_pipelines
    results = [f.get_result() for f in futures]
evalml/tests/automl_tests/dask_tests/test_dask_engine.py:94: in <listcomp>
    results = [f.get_result() for f in futures]
evalml/automl/engine/dask_engine.py:30: in get_result
    return self.work.result()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Future: cancelled, key: train_pipeline-4bd4a99325cd3cc91144f86b64d6503c>
timeout = None

    def result(self, timeout=None):
        """Wait until computation completes, gather result to local process.

        If *timeout* seconds are elapsed before returning, a
        ``dask.distributed.TimeoutError`` is raised.
        """
        if self.client.asynchronous:
            return self.client.sync(self._result, callback_timeout=timeout)

        # shorten error traceback
        result = self.client.sync(self._result, callback_timeout=timeout, raiseit=False)
        if self.status == "error":
            typ, exc, tb = result
            raise exc.with_traceback(tb)
        elif self.status == "cancelled":
>           raise result
E           concurrent.futures._base.CancelledError: train_pipeline-4bd4a99325cd3cc91144f86b64d6503c

Este parece ser um problema conhecido no dask https://github.com/dask/distributed/issues/4612

Todos 11 comentários

Basta adicionar alguns dados a partir deste 3,8 core deps para executar uma série de verificações. Adicionando os logs dessa execução.

github_unittests.txt

Acho que uma coisa que notei é que todos eles estão parando em torno da marca de 91-93% concluída. Duvido que haja algum valor em descobrir quais são esses testes, mas esse pode ser um caminho a seguir.

Aqui está outro para 3,9 dependências não essenciais.

github_unittests_2.txt

Obrigado por preencher @chukarsten

Felizmente, podemos descartar conda como uma causa, uma vez que isso acontece para nossas compilações de teste de unidade normais e não apenas para build_conda_pkg

Há alguma outra informação que devemos coletar que pode nos ajudar a descobrir isso? Algumas ideias abaixo

  • Com que segurança podemos reproduzir o tempo limite? Isso está acontecendo 50% do tempo em que executamos o trabalho de teste de unidade, mais, menos?
  • Qual teste ou testes não estão sendo concluídos corretamente? Se conseguirmos que o pytest registre o início e o fim de cada teste, podemos olhar os logs e deduzir qual teste não terminou quando o travamento ocorre. Isso parecia potencialmente útil.
  • Ainda veremos esses tempos limite se executarmos testes sem qualquer paralelização de pytest?
  • Isso é apenas um palpite, mas o que acontecerá se desativarmos os testes do mecanismo dask? Eu sei que vimos alguns flocos com aqueles recentemente # 2341
  • Como é a utilização da CPU e da memória durante a execução dos testes?

( @freddyaboulton eu adicionei você aqui, pois isso se conecta aos # 2298 e # 1815)

Alterando o Makefile para fazer o registro detalhado com o pytest, obtemos o seguinte registro
. Isso mostra que o último teste executado é "evalml / tuners / random_search_tuner.py :: evalml.tuners.random_search_tuner.RandomSearchTuner"

Acho que @freddyaboulton certamente está no este PR para separar os testes de unidade dask. Acho que temos a opção de não impedir a fusão em caso de falha deles. Este PR falhou em test_automl_immediate_quit, que ainda está na matriz de testes dask.

Analisar a causa raiz das falhas do teste de unidade dask é intrigante. Os logs geram muito disso:

distributed.worker - WARNING - Could not find data: {'Series-32a3ef2ca4739b46a6acc2ac58638b32': ['tcp://127.0.0.1:45587']} on workers: [] (who_has: {'Series-32a3ef2ca4739b46a6acc2ac58638b32': ['tcp://127.0.0.1:45587']})
distributed.scheduler - WARNING - Communication failed during replication: {'status': 'missing-data', 'keys'

Por que isso acontece? Bem, parece que onde quer que os dados que estão recebendo a ação estejam perdendo a referência a esses dados. Além disso, os 'trabalhadores: []' sugere que talvez o processo de babá esteja matando os trabalhadores. Eu suspeito que algo está acontecendo com a forma como os dados estão espalhados, mas também suspeito do que está acontecendo nos bastidores com esses quatro trabalhos executados juntos em pseudo paralelo / série.

Este problema distribuído do DASK sugere a desativação do escalonamento adaptativo para o cluster. Infelizmente, não usamos clusters adaptáveis, apenas clusters estáticos locais regulares, então esse não é o problema. Esse problema aponta a dispersão dos dados como a causa potencial do problema, em que os funcionários estão sendo abandonados, mas não estamos recebendo os mesmos erros de conexão.

Depois de tentar # 2376 separar os trabalhos dask e definir broadcast=False para o cliente do DaskEngine, por padrão, tive uma falha de teste instável com test_automl_immediate_quit. Documentado aqui .

Agora estou vendo o seguinte rastreamento de pilha em build_conda_pkg

[gw3] linux -- Python 3.7.10 $PREFIX/bin/python

X_y_binary_cls = (          0         1         2   ...        17        18        19
0  -0.039268  0.131912 -0.211206  ...  1.976989  ...ns], 0     0
1     0
2     1
3     1
4     1
     ..
95    1
96    1
97    1
98    1
99    0
Length: 100, dtype: int64)
cluster = LocalCluster(15c4b3ad, 'tcp://127.0.0.1:45201', workers=0, threads=0, memory=0 B)

    def test_submit_training_jobs_multiple(X_y_binary_cls, cluster):
        """Test that training multiple pipelines using the parallel engine produces the
        same results as the sequential engine."""
        X, y = X_y_binary_cls
        with Client(cluster) as client:
            pipelines = [
                BinaryClassificationPipeline(
                    component_graph=["Logistic Regression Classifier"],
                    parameters={"Logistic Regression Classifier": {"n_jobs": 1}},
                ),
                BinaryClassificationPipeline(component_graph=["Baseline Classifier"]),
                BinaryClassificationPipeline(component_graph=["SVM Classifier"]),
            ]

            def fit_pipelines(pipelines, engine):
                futures = []
                for pipeline in pipelines:
                    futures.append(
                        engine.submit_training_job(
                            X=X, y=y, automl_config=automl_data, pipeline=pipeline
                        )
                    )
                results = [f.get_result() for f in futures]
                return results

            # Verify all pipelines are trained and fitted.
            seq_pipelines = fit_pipelines(pipelines, SequentialEngine())
            for pipeline in seq_pipelines:
                assert pipeline._is_fitted

            # Verify all pipelines are trained and fitted.
>           par_pipelines = fit_pipelines(pipelines, DaskEngine(client=client))

evalml/tests/automl_tests/dask_tests/test_dask_engine.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
evalml/tests/automl_tests/dask_tests/test_dask_engine.py:94: in fit_pipelines
    results = [f.get_result() for f in futures]
evalml/tests/automl_tests/dask_tests/test_dask_engine.py:94: in <listcomp>
    results = [f.get_result() for f in futures]
evalml/automl/engine/dask_engine.py:30: in get_result
    return self.work.result()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Future: cancelled, key: train_pipeline-4bd4a99325cd3cc91144f86b64d6503c>
timeout = None

    def result(self, timeout=None):
        """Wait until computation completes, gather result to local process.

        If *timeout* seconds are elapsed before returning, a
        ``dask.distributed.TimeoutError`` is raised.
        """
        if self.client.asynchronous:
            return self.client.sync(self._result, callback_timeout=timeout)

        # shorten error traceback
        result = self.client.sync(self._result, callback_timeout=timeout, raiseit=False)
        if self.status == "error":
            typ, exc, tb = result
            raise exc.with_traceback(tb)
        elif self.status == "cancelled":
>           raise result
E           concurrent.futures._base.CancelledError: train_pipeline-4bd4a99325cd3cc91144f86b64d6503c

Este parece ser um problema conhecido no dask https://github.com/dask/distributed/issues/4612

Excluí minha postagem antiga, mas aqui está uma vermelha: https://github.com/alteryx/evalml/actions/runs/939673304 , parece ser o mesmo rastreamento de pilha @freddyaboulton postado acima.

Eu acredito que este problema não bloqueia mais por [este PR] para separar os trabalhos dask (https://github.com/alteryx/evalml/pull/2376), este PR para refatorar os trabalhos dask para reduzir os flocos, e este PR para fazer com que os trabalhos dask separados não bloqueiem para mesclar com o principal e este PR para adicionar um tempo limite para evitar que os testes patológicos dask demorem 6 horas para serem cancelados por Ações do GH.

Movendo para fechado porque os tempos limite relacionados ao dask não são mais um problema e não devem ser no futuro próximo. No entanto, a causa subjacente ainda é desconhecida.

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