Evalml: Délais d'expiration des tests unitaires (instabilité des données)

Créé le 9 juin 2021  ·  11Commentaires  ·  Source: alteryx/evalml

Nous voyons actuellement des tests unitaires atteindre la limite des actions GH de 6 heures. Ce n'est pas bon pour des raisons évidentes.

Délai d'expiration de 6 heures de profondeur de 3,8 cœurs (en cours)
build_conda_pkg, 3.8 core deps, 3.7 non core deps 6 h timeout (en cours)
3.7 délai d'attente de 6 heures pour les
3.8 non-core deps délai d'attente de 6 heures
3,7 deps non essentiels 1,5 h
build_conda_pkg
3.7 deps non essentiels
3.8

blocker bug testing

Commentaire le plus utile

Je vois maintenant le stacktrace suivant dans 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

Cela semble être un problème connu dans dask https://github.com/dask/distributed/issues/4612

Tous les 11 commentaires

Il suffit d'ajouter quelques données à partir de cette série de vérifications pour

github_unittests.txt

Je pense qu'une chose que j'ai remarquée, c'est qu'ils marquent tous une pause autour de la barre des 91 à 93 %. Je doute qu'il soit utile de déterminer de quels tests il s'agit, mais cela pourrait être une voie à suivre.

En voici un autre pour 3,9 deps non essentiels.

github_unittests_2.txt

Merci d'avoir déposé @chukarsten

Heureusement, nous pouvons exclure conda comme cause, car cela se produit pour nos versions de test unitaire normales et pas seulement pour build_conda_pkg

Y a-t-il d'autres informations que nous devrions collecter qui pourraient nous aider à comprendre cela? Quelques idées ci-dessous

  • Avec quelle fiabilité pouvons-nous reproduire le délai d'attente ? Est-ce que cela se produit 50% du temps où nous exécutons un travail de test unitaire, plus, moins ?
  • Quel(s) test(s) ne se termine pas correctement ? Si nous pouvons faire en sorte que pytest enregistre le début et la fin de chaque test, nous pouvons examiner les journaux et en déduire quel test n'est pas terminé lorsque le blocage se produit. Cela semblait potentiellement utile.
  • Voyons-nous toujours ces délais d'attente si nous exécutons des tests sans aucune parallélisation pytest ?
  • Ce n'est qu'une intuition, mais que se passe-t-il si nous désactivons les tests du moteur dask ? Je sais que nous avons vu des flocons avec ceux récemment # 2341
  • À quoi ressemble l'utilisation du processeur et de la mémoire pendant l'exécution des tests ?

( @freddyaboulton je vous ai ajouté ici puisque cela se connecte aux #2298 et #1815)

En changeant le Makefile pour effectuer une journalisation détaillée avec pytest, nous obtenons le journal suivant
. Cela montre que le dernier test exécuté est "evalml/tuners/random_search_tuner.py::evalml.tuners.random_search_tuner.RandomSearchTuner"

Je pense que @freddyaboulton est certainement sur quelque chose ici et nous ce PR pour séparer les tests unitaires de base. Je pense que nous avons la possibilité de ne pas empêcher la fusion en cas d'échec. Ce PR a échoué sur test_automl_immediate_quit, qui est toujours dans le tableau des tests dask.

La recherche de la cause première des échecs des tests unitaires du dask est déroutant. Les logs génèrent beaucoup de ceci :

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'

Pourquoi cela arrive-t-il? Eh bien, il semble que partout où les données sur lesquelles on agit perd la référence à ces données. De plus, les « travailleurs : [] » suggèrent que le processus de la nounou tue peut-être les travailleurs. Je soupçonne qu'il se passe quelque chose avec la façon dont les données sont dispersées, mais je me méfie également de ce qui se passe sous les couvertures avec ces quatre travaux fonctionnant ensemble en pseudo parallèle/série.

Ce problème de distribution dask suggère de désactiver la mise à l'échelle adaptative pour le cluster. Malheureusement, nous n'utilisons pas de clusters adaptatifs, juste des clusters locaux et statiques réguliers, ce n'est donc pas le problème. Ce problème indique que la dispersion des données est la cause potentielle du problème, où les travailleurs sont abandonnés, mais nous n'obtenons pas les mêmes erreurs de connexion.

Après avoir essayé #2376 de séparer les tâches dask et de définir broadcast=False pour le client de DaskEngine, par défaut, j'ai un échec de test floconneux avec test_automl_immediate_quit. Documenté ici .

Je vois maintenant le stacktrace suivant dans 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

Cela semble être un problème connu dans dask https://github.com/dask/distributed/issues/4612

J'ai supprimé mon ancien message mais en voici un rouge : https://github.com/alteryx/evalml/actions/runs/939673304 , semble être la même trace de pile @freddyaboulton publiée ci-dessus.

Je crois que ce problème ne bloque plus par [ce PR] pour séparer les travaux dask (https://github.com/alteryx/evalml/pull/2376), ce PR pour refactoriser les travaux dask pour réduire les flocons, et ce PR pour que les tâches dask séparées ne bloquent pas la fusion avec le principal et ce PR pour ajouter un délai d'attente pour empêcher les tests dask pathologiques de prendre 6 heures pour être finalement annulés par GH Actions.

Je vais déplacer cela sur fermé car les délais d'attente liés au dask ne sont plus un problème et ne devraient pas l'être dans un avenir prévisible. Cependant, la cause sous-jacente est encore inconnue.

Cette page vous a été utile?
0 / 5 - 0 notes